From 7db39fb5665d283fc4e58a590a03130f36313a4c Mon Sep 17 00:00:00 2001 From: "Xiaokang Wang (Shelikhoo)" Date: Thu, 22 Aug 2024 04:05:05 +0100 Subject: [PATCH] Add (Experimental) Meyka Building Blocks to request Transport (#3120) * add packetconn assembler * let kcp use environment dependency injection * Add destination override to simplified setting * add dtls dialer * add dtls listener * add dtls to default * fix bugs * add debug options to freedom outbound * fix kcp test failure for transport environment --- go.mod | 3 +- go.sum | 1 + main/distro/all/all.go | 4 + proxy/freedom/config.pb.go | 175 +++++-- proxy/freedom/config.proto | 10 + proxy/freedom/freedom.go | 15 +- transport/internet/dtls/config.pb.go | 236 ++++++++++ transport/internet/dtls/config.proto | 25 + transport/internet/dtls/dialer.go | 57 +++ transport/internet/dtls/dtls.go | 16 + transport/internet/dtls/errors.generated.go | 9 + transport/internet/dtls/listener.go | 212 +++++++++ transport/internet/kcp/dialer.go | 7 +- transport/internet/kcp/kcp_test.go | 19 +- .../assembler/packetconn/errors.generated.go | 9 + .../assembler/packetconn/packetConn.pb.go | 342 ++++++++++++++ .../assembler/packetconn/packetConn.proto | 35 ++ .../assembler/packetconn/packetbundle.go | 48 ++ .../assembler/packetconn/packetconn.go | 3 + .../assembler/packetconn/req2packet.go | 429 ++++++++++++++++++ .../assembler/packetconn/udpassembler.go | 78 ++++ .../packetconn/udpassemblerClient.go | 81 ++++ .../packetconn/udpassemblerServer.go | 166 +++++++ transport/internet/request/assembly/hub.go | 11 + transport/internet/request/roundtripper.go | 10 + .../request/roundtripper/httprt/config.pb.go | 88 ++-- .../request/roundtripper/httprt/config.proto | 2 + .../request/roundtripper/httprt/httprt.go | 67 ++- transport/internet/tls/config.go | 11 + .../internet/transportcommon/httpDialer.go | 57 ++- transport/internet/udp/hub.go | 121 +++-- 31 files changed, 2219 insertions(+), 128 deletions(-) create mode 100644 transport/internet/dtls/config.pb.go create mode 100644 transport/internet/dtls/config.proto create mode 100644 transport/internet/dtls/dialer.go create mode 100644 transport/internet/dtls/dtls.go create mode 100644 transport/internet/dtls/errors.generated.go create mode 100644 transport/internet/dtls/listener.go create mode 100644 transport/internet/request/assembler/packetconn/errors.generated.go create mode 100644 transport/internet/request/assembler/packetconn/packetConn.pb.go create mode 100644 transport/internet/request/assembler/packetconn/packetConn.proto create mode 100644 transport/internet/request/assembler/packetconn/packetbundle.go create mode 100644 transport/internet/request/assembler/packetconn/packetconn.go create mode 100644 transport/internet/request/assembler/packetconn/req2packet.go create mode 100644 transport/internet/request/assembler/packetconn/udpassembler.go create mode 100644 transport/internet/request/assembler/packetconn/udpassemblerClient.go create mode 100644 transport/internet/request/assembler/packetconn/udpassemblerServer.go diff --git a/go.mod b/go.mod index 514c59870..a15c03de0 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/render v1.0.3 github.com/go-playground/validator/v10 v10.20.0 + github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 github.com/google/go-cmp v0.6.0 @@ -19,6 +20,7 @@ require ( github.com/miekg/dns v1.1.59 github.com/mustafaturan/bus v1.0.2 github.com/pelletier/go-toml v1.9.5 + github.com/pion/dtls/v2 v2.2.7 github.com/pion/transport/v2 v2.2.5 github.com/pires/go-proxyproto v0.7.0 github.com/quic-go/quic-go v0.43.0 @@ -67,7 +69,6 @@ require ( 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 - github.com/pion/dtls/v2 v2.2.7 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/sctp v1.8.7 // indirect diff --git a/go.sum b/go.sum index 262e14c6d..e0710a5f7 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,7 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 200f12283..16ca28b09 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -74,8 +74,12 @@ import ( _ "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembler/simple" _ "github.com/v2fly/v2ray-core/v5/transport/internet/request/roundtripper/httprt" + _ "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembler/packetconn" + _ "github.com/v2fly/v2ray-core/v5/transport/internet/request/stereotype/meek" + _ "github.com/v2fly/v2ray-core/v5/transport/internet/dtls" + _ "github.com/v2fly/v2ray-core/v5/transport/internet/httpupgrade" // Transport headers diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index 0669c70fd..0574b69e2 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -16,6 +16,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ProtocolReplacement int32 + +const ( + ProtocolReplacement_IDENTITY ProtocolReplacement = 0 + ProtocolReplacement_FORCE_TCP ProtocolReplacement = 1 + ProtocolReplacement_FORCE_UDP ProtocolReplacement = 2 +) + +// Enum value maps for ProtocolReplacement. +var ( + ProtocolReplacement_name = map[int32]string{ + 0: "IDENTITY", + 1: "FORCE_TCP", + 2: "FORCE_UDP", + } + ProtocolReplacement_value = map[string]int32{ + "IDENTITY": 0, + "FORCE_TCP": 1, + "FORCE_UDP": 2, + } +) + +func (x ProtocolReplacement) Enum() *ProtocolReplacement { + p := new(ProtocolReplacement) + *p = x + return p +} + +func (x ProtocolReplacement) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProtocolReplacement) Descriptor() protoreflect.EnumDescriptor { + return file_proxy_freedom_config_proto_enumTypes[0].Descriptor() +} + +func (ProtocolReplacement) Type() protoreflect.EnumType { + return &file_proxy_freedom_config_proto_enumTypes[0] +} + +func (x ProtocolReplacement) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProtocolReplacement.Descriptor instead. +func (ProtocolReplacement) EnumDescriptor() ([]byte, []int) { + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{0} +} + type Config_DomainStrategy int32 const ( @@ -52,11 +101,11 @@ func (x Config_DomainStrategy) String() string { } func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor { - return file_proxy_freedom_config_proto_enumTypes[0].Descriptor() + return file_proxy_freedom_config_proto_enumTypes[1].Descriptor() } func (Config_DomainStrategy) Type() protoreflect.EnumType { - return &file_proxy_freedom_config_proto_enumTypes[0] + return &file_proxy_freedom_config_proto_enumTypes[1] } func (x Config_DomainStrategy) Number() protoreflect.EnumNumber { @@ -125,6 +174,7 @@ type Config struct { Timeout uint32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` DestinationOverride *DestinationOverride `protobuf:"bytes,3,opt,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` UserLevel uint32 `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` + ProtocolReplacement ProtocolReplacement `protobuf:"varint,5,opt,name=protocol_replacement,json=protocolReplacement,proto3,enum=v2ray.core.proxy.freedom.ProtocolReplacement" json:"protocol_replacement,omitempty"` } func (x *Config) Reset() { @@ -188,10 +238,20 @@ func (x *Config) GetUserLevel() uint32 { return 0 } +func (x *Config) GetProtocolReplacement() ProtocolReplacement { + if x != nil { + return x.ProtocolReplacement + } + return ProtocolReplacement_IDENTITY +} + type SimplifiedConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + DestinationOverride *DestinationOverride `protobuf:"bytes,3,opt,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` + ProtocolReplacement ProtocolReplacement `protobuf:"varint,5,opt,name=protocol_replacement,json=protocolReplacement,proto3,enum=v2ray.core.proxy.freedom.ProtocolReplacement" json:"protocol_replacement,omitempty"` } func (x *SimplifiedConfig) Reset() { @@ -226,6 +286,20 @@ func (*SimplifiedConfig) Descriptor() ([]byte, []int) { return file_proxy_freedom_config_proto_rawDescGZIP(), []int{2} } +func (x *SimplifiedConfig) GetDestinationOverride() *DestinationOverride { + if x != nil { + return x.DestinationOverride + } + return nil +} + +func (x *SimplifiedConfig) GetProtocolReplacement() ProtocolReplacement { + if x != nil { + return x.ProtocolReplacement + } + return ProtocolReplacement_IDENTITY +} + var File_proxy_freedom_config_proto protoreflect.FileDescriptor var file_proxy_freedom_config_proto_rawDesc = []byte{ @@ -242,7 +316,7 @@ var file_proxy_freedom_config_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xc4, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xa6, 0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x58, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, @@ -258,21 +332,44 @@ var file_proxy_freedom_config_proto_rawDesc = []byte{ 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x41, 0x0a, 0x0e, 0x44, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, - 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, - 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x22, 0x2b, 0x0a, - 0x10, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x3a, 0x17, 0x82, 0xb5, 0x18, 0x13, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x12, 0x07, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x42, 0x69, 0x0a, 0x1c, 0x63, 0x6f, - 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a, 0x2c, 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, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, - 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x46, 0x72, - 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x60, 0x0a, 0x14, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, + 0x64, 0x6f, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x41, 0x0a, 0x0e, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, + 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, + 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, + 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x22, + 0xef, 0x01, 0x0a, 0x10, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x60, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, + 0x65, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, + 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x60, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x17, 0x82, 0xb5, 0x18, 0x13, 0x0a, 0x08, + 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x07, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, + 0x6d, 0x2a, 0x41, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0c, 0x0a, 0x08, 0x49, 0x44, 0x45, 0x4e, + 0x54, 0x49, 0x54, 0x59, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, + 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x55, + 0x44, 0x50, 0x10, 0x02, 0x42, 0x69, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, + 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, + 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a, 0x2c, 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, 0x66, 0x72, 0x65, + 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, + 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -287,24 +384,28 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte { return file_proxy_freedom_config_proto_rawDescData } -var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_proxy_freedom_config_proto_goTypes = []interface{}{ - (Config_DomainStrategy)(0), // 0: v2ray.core.proxy.freedom.Config.DomainStrategy - (*DestinationOverride)(nil), // 1: v2ray.core.proxy.freedom.DestinationOverride - (*Config)(nil), // 2: v2ray.core.proxy.freedom.Config - (*SimplifiedConfig)(nil), // 3: v2ray.core.proxy.freedom.SimplifiedConfig - (*protocol.ServerEndpoint)(nil), // 4: v2ray.core.common.protocol.ServerEndpoint +var file_proxy_freedom_config_proto_goTypes = []any{ + (ProtocolReplacement)(0), // 0: v2ray.core.proxy.freedom.ProtocolReplacement + (Config_DomainStrategy)(0), // 1: v2ray.core.proxy.freedom.Config.DomainStrategy + (*DestinationOverride)(nil), // 2: v2ray.core.proxy.freedom.DestinationOverride + (*Config)(nil), // 3: v2ray.core.proxy.freedom.Config + (*SimplifiedConfig)(nil), // 4: v2ray.core.proxy.freedom.SimplifiedConfig + (*protocol.ServerEndpoint)(nil), // 5: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_freedom_config_proto_depIdxs = []int32{ - 4, // 0: v2ray.core.proxy.freedom.DestinationOverride.server:type_name -> v2ray.core.common.protocol.ServerEndpoint - 0, // 1: v2ray.core.proxy.freedom.Config.domain_strategy:type_name -> v2ray.core.proxy.freedom.Config.DomainStrategy - 1, // 2: v2ray.core.proxy.freedom.Config.destination_override:type_name -> v2ray.core.proxy.freedom.DestinationOverride - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 5, // 0: v2ray.core.proxy.freedom.DestinationOverride.server:type_name -> v2ray.core.common.protocol.ServerEndpoint + 1, // 1: v2ray.core.proxy.freedom.Config.domain_strategy:type_name -> v2ray.core.proxy.freedom.Config.DomainStrategy + 2, // 2: v2ray.core.proxy.freedom.Config.destination_override:type_name -> v2ray.core.proxy.freedom.DestinationOverride + 0, // 3: v2ray.core.proxy.freedom.Config.protocol_replacement:type_name -> v2ray.core.proxy.freedom.ProtocolReplacement + 2, // 4: v2ray.core.proxy.freedom.SimplifiedConfig.destination_override:type_name -> v2ray.core.proxy.freedom.DestinationOverride + 0, // 5: v2ray.core.proxy.freedom.SimplifiedConfig.protocol_replacement:type_name -> v2ray.core.proxy.freedom.ProtocolReplacement + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_proxy_freedom_config_proto_init() } @@ -313,7 +414,7 @@ func file_proxy_freedom_config_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_proxy_freedom_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_proxy_freedom_config_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*DestinationOverride); i { case 0: return &v.state @@ -325,7 +426,7 @@ func file_proxy_freedom_config_proto_init() { return nil } } - file_proxy_freedom_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_proxy_freedom_config_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Config); i { case 0: return &v.state @@ -337,7 +438,7 @@ func file_proxy_freedom_config_proto_init() { return nil } } - file_proxy_freedom_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_proxy_freedom_config_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*SimplifiedConfig); i { case 0: return &v.state @@ -355,7 +456,7 @@ func file_proxy_freedom_config_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_freedom_config_proto_rawDesc, - NumEnums: 1, + NumEnums: 2, NumMessages: 3, NumExtensions: 0, NumServices: 0, diff --git a/proxy/freedom/config.proto b/proxy/freedom/config.proto index e095dc8ee..78c8631d3 100644 --- a/proxy/freedom/config.proto +++ b/proxy/freedom/config.proto @@ -13,6 +13,12 @@ message DestinationOverride { v2ray.core.common.protocol.ServerEndpoint server = 1; } +enum ProtocolReplacement { + IDENTITY = 0; + FORCE_TCP = 1; + FORCE_UDP = 2; +} + message Config { enum DomainStrategy { AS_IS = 0; @@ -24,9 +30,13 @@ message Config { uint32 timeout = 2 [deprecated = true]; DestinationOverride destination_override = 3; uint32 user_level = 4; + ProtocolReplacement protocol_replacement = 5; } message SimplifiedConfig { option (v2ray.core.common.protoext.message_opt).type = "outbound"; option (v2ray.core.common.protoext.message_opt).short_name = "freedom"; + + DestinationOverride destination_override = 3; + ProtocolReplacement protocol_replacement = 5; } \ No newline at end of file diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index e16e72b8a..2f70b7332 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -35,7 +35,10 @@ func init() { common.Must(common.RegisterConfig((*SimplifiedConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { simplifiedServer := config.(*SimplifiedConfig) _ = simplifiedServer - fullConfig := &Config{} + fullConfig := &Config{ + DestinationOverride: simplifiedServer.DestinationOverride, + ProtocolReplacement: simplifiedServer.ProtocolReplacement, + } return common.CreateObject(ctx, fullConfig) })) } @@ -104,6 +107,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte destination.Port = net.Port(server.Port) } } + if h.config.ProtocolReplacement != ProtocolReplacement_IDENTITY { + if h.config.ProtocolReplacement == ProtocolReplacement_FORCE_TCP { + destination.Network = net.Network_TCP + } + if h.config.ProtocolReplacement == ProtocolReplacement_FORCE_UDP { + destination.Network = net.Network_UDP + } + } if h.config.useIP() { outbound.Resolver = func(ctx context.Context, domain string) net.Address { return h.resolveIP(ctx, domain, dialer.Address()) @@ -153,7 +164,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) var reader buf.Reader - if destination.Network == net.Network_TCP { + if destination.Network == net.Network_TCP && h.config.ProtocolReplacement == ProtocolReplacement_IDENTITY { reader = buf.NewReader(conn) } else { reader = buf.NewPacketReader(conn) diff --git a/transport/internet/dtls/config.pb.go b/transport/internet/dtls/config.pb.go new file mode 100644 index 000000000..658b20feb --- /dev/null +++ b/transport/internet/dtls/config.pb.go @@ -0,0 +1,236 @@ +package dtls + +import ( + _ "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 DTLSMode int32 + +const ( + DTLSMode_INVALID DTLSMode = 0 + DTLSMode_PSK DTLSMode = 1 +) + +// Enum value maps for DTLSMode. +var ( + DTLSMode_name = map[int32]string{ + 0: "INVALID", + 1: "PSK", + } + DTLSMode_value = map[string]int32{ + "INVALID": 0, + "PSK": 1, + } +) + +func (x DTLSMode) Enum() *DTLSMode { + p := new(DTLSMode) + *p = x + return p +} + +func (x DTLSMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DTLSMode) Descriptor() protoreflect.EnumDescriptor { + return file_transport_internet_dtls_config_proto_enumTypes[0].Descriptor() +} + +func (DTLSMode) Type() protoreflect.EnumType { + return &file_transport_internet_dtls_config_proto_enumTypes[0] +} + +func (x DTLSMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DTLSMode.Descriptor instead. +func (DTLSMode) EnumDescriptor() ([]byte, []int) { + return file_transport_internet_dtls_config_proto_rawDescGZIP(), []int{0} +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mode DTLSMode `protobuf:"varint,1,opt,name=mode,proto3,enum=v2ray.core.transport.internet.dtls.DTLSMode" json:"mode,omitempty"` + Psk []byte `protobuf:"bytes,2,opt,name=psk,proto3" json:"psk,omitempty"` + Mtu uint32 `protobuf:"varint,3,opt,name=mtu,proto3" json:"mtu,omitempty"` + ReplayProtectionWindow uint32 `protobuf:"varint,4,opt,name=replay_protection_window,json=replayProtectionWindow,proto3" json:"replay_protection_window,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_dtls_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_dtls_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_dtls_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetMode() DTLSMode { + if x != nil { + return x.Mode + } + return DTLSMode_INVALID +} + +func (x *Config) GetPsk() []byte { + if x != nil { + return x.Psk + } + return nil +} + +func (x *Config) GetMtu() uint32 { + if x != nil { + return x.Mtu + } + return 0 +} + +func (x *Config) GetReplayProtectionWindow() uint32 { + if x != nil { + return x.ReplayProtectionWindow + } + return 0 +} + +var File_transport_internet_dtls_config_proto protoreflect.FileDescriptor + +var file_transport_internet_dtls_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, 0x64, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x74, 0x6c, 0x73, 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, 0xbf, 0x01, 0x0a, + 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x74, 0x6c, 0x73, 0x2e, 0x44, 0x54, 0x4c, 0x53, 0x4d, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x73, 0x6b, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x70, 0x73, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x74, 0x75, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x38, 0x0a, + 0x18, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x3a, 0x15, 0x82, 0xb5, 0x18, 0x11, 0x0a, 0x09, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x04, 0x64, 0x74, 0x6c, 0x73, 0x2a, 0x20, + 0x0a, 0x08, 0x44, 0x54, 0x4c, 0x53, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x53, 0x4b, 0x10, 0x01, + 0x42, 0x87, 0x01, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x36, 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, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2f, 0x64, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, + 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x74, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_transport_internet_dtls_config_proto_rawDescOnce sync.Once + file_transport_internet_dtls_config_proto_rawDescData = file_transport_internet_dtls_config_proto_rawDesc +) + +func file_transport_internet_dtls_config_proto_rawDescGZIP() []byte { + file_transport_internet_dtls_config_proto_rawDescOnce.Do(func() { + file_transport_internet_dtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_dtls_config_proto_rawDescData) + }) + return file_transport_internet_dtls_config_proto_rawDescData +} + +var file_transport_internet_dtls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_transport_internet_dtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_dtls_config_proto_goTypes = []any{ + (DTLSMode)(0), // 0: v2ray.core.transport.internet.dtls.DTLSMode + (*Config)(nil), // 1: v2ray.core.transport.internet.dtls.Config +} +var file_transport_internet_dtls_config_proto_depIdxs = []int32{ + 0, // 0: v2ray.core.transport.internet.dtls.Config.mode:type_name -> v2ray.core.transport.internet.dtls.DTLSMode + 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_transport_internet_dtls_config_proto_init() } +func file_transport_internet_dtls_config_proto_init() { + if File_transport_internet_dtls_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_dtls_config_proto_msgTypes[0].Exporter = func(v any, i int) any { + 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_dtls_config_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_dtls_config_proto_goTypes, + DependencyIndexes: file_transport_internet_dtls_config_proto_depIdxs, + EnumInfos: file_transport_internet_dtls_config_proto_enumTypes, + MessageInfos: file_transport_internet_dtls_config_proto_msgTypes, + }.Build() + File_transport_internet_dtls_config_proto = out.File + file_transport_internet_dtls_config_proto_rawDesc = nil + file_transport_internet_dtls_config_proto_goTypes = nil + file_transport_internet_dtls_config_proto_depIdxs = nil +} diff --git a/transport/internet/dtls/config.proto b/transport/internet/dtls/config.proto new file mode 100644 index 000000000..79992880a --- /dev/null +++ b/transport/internet/dtls/config.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package v2ray.core.transport.internet.dtls; +option csharp_namespace = "V2Ray.Core.Transport.Internet.Dtls"; +option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/dtls"; +option java_package = "com.v2ray.core.transport.internet.dtls"; +option java_multiple_files = true; + +import "common/protoext/extensions.proto"; + +enum DTLSMode { + INVALID = 0; + PSK = 1; +} + +message Config { + option (v2ray.core.common.protoext.message_opt).type = "transport"; + option (v2ray.core.common.protoext.message_opt).short_name = "dtls"; + + DTLSMode mode = 1; + bytes psk = 2; + + uint32 mtu = 3; + uint32 replay_protection_window = 4; +} \ No newline at end of file diff --git a/transport/internet/dtls/dialer.go b/transport/internet/dtls/dialer.go new file mode 100644 index 000000000..882fbe5a8 --- /dev/null +++ b/transport/internet/dtls/dialer.go @@ -0,0 +1,57 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2" + + "github.com/v2fly/v2ray-core/v5/common" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/v2fly/v2ray-core/v5/common/net" + "github.com/v2fly/v2ray-core/v5/common/session" + "github.com/v2fly/v2ray-core/v5/transport/internet" +) + +func dialDTLS(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { + transportConfiguration := streamSettings.ProtocolSettings.(*Config) + + newError("dialing DTLS to ", dest).WriteToLog() + + transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment) + dialer := transportEnvironment.Dialer() + + rawConn, err := dialer.Dial(ctx, nil, dest, streamSettings.SocketSettings) + if err != nil { + return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err) + } + config := &dtls.Config{} + config.MTU = int(transportConfiguration.Mtu) + config.ReplayProtectionWindow = int(transportConfiguration.ReplayProtectionWindow) + + switch transportConfiguration.Mode { + case DTLSMode_PSK: + config.PSK = func(bytes []byte) ([]byte, error) { + return transportConfiguration.Psk, nil + } + config.PSKIdentityHint = []byte("") + config.CipherSuites = []dtls.CipherSuiteID{dtls.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256} + default: + return nil, newError("unknow dtls mode") + } + return dtls.Client(rawConn, config) +} + +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 := dialDTLS(ctx, dest, streamSettings) + if err != nil { + return nil, newError("failed to dial request to ", dest).Base(err) + } + return internet.Connection(conn), nil +} + +func init() { + common.Must(internet.RegisterTransportDialer(protocolName, dial)) +} diff --git a/transport/internet/dtls/dtls.go b/transport/internet/dtls/dtls.go new file mode 100644 index 000000000..a547a0bd0 --- /dev/null +++ b/transport/internet/dtls/dtls.go @@ -0,0 +1,16 @@ +package dtls + +import ( + "github.com/v2fly/v2ray-core/v5/common" + "github.com/v2fly/v2ray-core/v5/transport/internet" +) + +//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen + +const protocolName = "dtls" + +func init() { + common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { + return new(Config) + })) +} diff --git a/transport/internet/dtls/errors.generated.go b/transport/internet/dtls/errors.generated.go new file mode 100644 index 000000000..2cf2f117e --- /dev/null +++ b/transport/internet/dtls/errors.generated.go @@ -0,0 +1,9 @@ +package dtls + +import "github.com/v2fly/v2ray-core/v5/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/transport/internet/dtls/listener.go b/transport/internet/dtls/listener.go new file mode 100644 index 000000000..36514a9a2 --- /dev/null +++ b/transport/internet/dtls/listener.go @@ -0,0 +1,212 @@ +package dtls + +import ( + "context" + "io" + gonet "net" + "sync" + "time" + + "github.com/pion/dtls/v2" + + "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/transport/internet" + "github.com/v2fly/v2ray-core/v5/transport/internet/udp" +) + +type Listener struct { + config *Config + + sync.Mutex + addConn internet.ConnHandler + hub *udp.Hub + + sessions map[ConnectionID]*dTLSConnWrapped +} + +func (l *Listener) Close() error { + return l.hub.Close() +} + +func (l *Listener) Addr() net.Addr { + return l.hub.Addr() +} + +type ConnectionID struct { + Remote net.Address + Port net.Port +} + +func newDTLSServerConn(src net.Destination, parent *Listener) *dTLSConn { + ctx := context.Background() + ctx, finish := context.WithCancel(ctx) + return &dTLSConn{ + src: src, + parent: parent, + readChan: make(chan *buf.Buffer, 256), + ctx: ctx, + finish: finish, + } +} + +type dTLSConnWrapped struct { + unencryptedConn *dTLSConn + dTLSConn *dtls.Conn +} + +type dTLSConn struct { + src net.Destination + parent *Listener + + readChan chan *buf.Buffer + ctx context.Context + finish func() +} + +func (l *dTLSConn) Read(b []byte) (n int, err error) { + select { + case pack := <-l.readChan: + n := copy(b, pack.Bytes()) + defer pack.Release() + if n < int(pack.Len()) { + return n, io.ErrShortBuffer + } + return n, nil + case <-l.ctx.Done(): + return 0, l.ctx.Err() + } +} + +func (l *dTLSConn) Write(b []byte) (n int, err error) { + return l.parent.hub.WriteTo(b, l.src) +} + +func (l *dTLSConn) Close() error { + l.finish() + l.parent.Remove(l.src) + return nil +} + +func (l *dTLSConn) LocalAddr() gonet.Addr { + return nil +} + +func (l *dTLSConn) RemoteAddr() gonet.Addr { + return &net.UDPAddr{ + IP: l.src.Address.IP(), + Port: int(l.src.Port.Value()), + } +} + +func (l *dTLSConn) SetDeadline(t time.Time) error { + return nil +} + +func (l *dTLSConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (l *dTLSConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func (l *dTLSConn) OnReceive(payload *buf.Buffer) { + select { + case l.readChan <- payload: + default: + } +} + +func NewListener(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (*Listener, error) { + transportConfiguration := streamSettings.ProtocolSettings.(*Config) + hub, err := udp.ListenUDP(ctx, address, port, streamSettings, udp.HubCapacity(1024)) + if err != nil { + return nil, err + } + l := &Listener{ + addConn: addConn, + config: transportConfiguration, + sessions: make(map[ConnectionID]*dTLSConnWrapped), + } + l.Lock() + l.hub = hub + l.Unlock() + newError("listening on ", address, ":", port).WriteToLog() + + go l.handlePackets() + return l, err +} + +func (l *Listener) handlePackets() { + receive := l.hub.Receive() + for payload := range receive { + l.OnReceive(payload.Payload, payload.Source) + } +} + +func newDTLSConnWrapped(unencryptedConnection *dTLSConn, transportConfiguration *Config) (*dtls.Conn, error) { + config := &dtls.Config{} + config.MTU = int(transportConfiguration.Mtu) + config.ReplayProtectionWindow = int(transportConfiguration.ReplayProtectionWindow) + + switch transportConfiguration.Mode { + case DTLSMode_PSK: + config.PSK = func(bytes []byte) ([]byte, error) { + return transportConfiguration.Psk, nil + } + config.PSKIdentityHint = []byte("") + config.CipherSuites = []dtls.CipherSuiteID{dtls.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256} + default: + newError("unknown dtls mode").WriteToLog() + } + dtlsConn, err := dtls.Server(unencryptedConnection, config) + if err != nil { + return nil, newError("unable to create dtls server conn").Base(err) + } + return dtlsConn, err +} + +func (l *Listener) OnReceive(payload *buf.Buffer, src net.Destination) { + id := ConnectionID{ + Remote: src.Address, + Port: src.Port, + } + l.Lock() + defer l.Unlock() + conn, found := l.sessions[id] + if !found { + var err error + unEncryptedConn := newDTLSServerConn(src, l) + conn = &dTLSConnWrapped{unencryptedConn: unEncryptedConn} + l.sessions[id] = conn + go func() { + conn.dTLSConn, err = newDTLSConnWrapped(unEncryptedConn, l.config) + if err != nil { + newError("unable to accept new dtls connection").Base(err).WriteToLog() + return + } + l.addConn(internet.Connection(conn.dTLSConn)) + }() + } + conn.unencryptedConn.OnReceive(payload) +} + +func (l *Listener) Remove(src net.Destination) { + l.Lock() + defer l.Unlock() + id := ConnectionID{ + Remote: src.Address, + Port: src.Port, + } + delete(l.sessions, id) +} + +func ListenDTLS(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) { + return NewListener(ctx, address, port, streamSettings, addConn) +} + +func init() { + common.Must(internet.RegisterTransportListener(protocolName, ListenDTLS)) +} diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index 68b56c190..3084e19d6 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -8,6 +8,8 @@ import ( "github.com/v2fly/v2ray-core/v5/common" "github.com/v2fly/v2ray-core/v5/common/buf" "github.com/v2fly/v2ray-core/v5/common/dice" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" "github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/transport/internet" "github.com/v2fly/v2ray-core/v5/transport/internet/tls" @@ -47,7 +49,10 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet dest.Network = net.Network_UDP newError("dialing mKCP to ", dest).WriteToLog() - rawConn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) + transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment) + dialer := transportEnvironment.Dialer() + + rawConn, err := dialer.Dial(ctx, nil, dest, streamSettings.SocketSettings) if err != nil { return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err) } diff --git a/transport/internet/kcp/kcp_test.go b/transport/internet/kcp/kcp_test.go index d10534a27..5e37b35ea 100644 --- a/transport/internet/kcp/kcp_test.go +++ b/transport/internet/kcp/kcp_test.go @@ -7,6 +7,11 @@ import ( "testing" "time" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl" + "github.com/v2fly/v2ray-core/v5/common/environment/transientstorageimpl" + "github.com/google/go-cmp/cmp" "golang.org/x/sync/errgroup" @@ -18,7 +23,17 @@ import ( ) func TestDialAndListen(t *testing.T) { - listener, err := NewListener(context.Background(), net.LocalHostIP, net.Port(0), &internet.MemoryStreamConfig{ + ctx := context.Background() + defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault() + rootEnv := environment.NewRootEnvImpl(ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener()) + proxyEnvironment := rootEnv.ProxyEnvironment("o") + transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("kcp") + if err != nil { + t.Fatal(err) + } + ctx = envctx.ContextWithEnvironment(ctx, transportEnvironment) + + listener, err := NewListener(ctx, net.LocalHostIP, net.Port(0), &internet.MemoryStreamConfig{ ProtocolName: "mkcp", ProtocolSettings: &Config{}, }, func(conn internet.Connection) { @@ -45,7 +60,7 @@ func TestDialAndListen(t *testing.T) { var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(func() error { - clientConn, err := DialKCP(context.Background(), net.UDPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ + clientConn, err := DialKCP(ctx, net.UDPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ ProtocolName: "mkcp", ProtocolSettings: &Config{}, }) diff --git a/transport/internet/request/assembler/packetconn/errors.generated.go b/transport/internet/request/assembler/packetconn/errors.generated.go new file mode 100644 index 000000000..e7fcdf4dc --- /dev/null +++ b/transport/internet/request/assembler/packetconn/errors.generated.go @@ -0,0 +1,9 @@ +package packetconn + +import "github.com/v2fly/v2ray-core/v5/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/transport/internet/request/assembler/packetconn/packetConn.pb.go b/transport/internet/request/assembler/packetconn/packetConn.pb.go new file mode 100644 index 000000000..7fec32445 --- /dev/null +++ b/transport/internet/request/assembler/packetconn/packetConn.pb.go @@ -0,0 +1,342 @@ +package packetconn + +import ( + _ "github.com/v2fly/v2ray-core/v5/common/protoext" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + 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 + + UnderlyingTransportSetting *anypb.Any `protobuf:"bytes,1,opt,name=underlying_transport_setting,json=underlyingTransportSetting,proto3" json:"underlying_transport_setting,omitempty"` + UnderlyingTransportName string `protobuf:"bytes,2,opt,name=underlying_transport_name,json=underlyingTransportName,proto3" json:"underlying_transport_name,omitempty"` + MaxWriteDelay int32 `protobuf:"varint,3,opt,name=max_write_delay,json=maxWriteDelay,proto3" json:"max_write_delay,omitempty"` + MaxRequestSize int32 `protobuf:"varint,4,opt,name=max_request_size,json=maxRequestSize,proto3" json:"max_request_size,omitempty"` + PollingIntervalInitial int32 `protobuf:"varint,5,opt,name=polling_interval_initial,json=pollingIntervalInitial,proto3" json:"polling_interval_initial,omitempty"` +} + +func (x *ClientConfig) Reset() { + *x = ClientConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_request_assembler_packetconn_packetConn_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_transport_internet_request_assembler_packetconn_packetConn_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_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescGZIP(), []int{0} +} + +func (x *ClientConfig) GetUnderlyingTransportSetting() *anypb.Any { + if x != nil { + return x.UnderlyingTransportSetting + } + return nil +} + +func (x *ClientConfig) GetUnderlyingTransportName() string { + if x != nil { + return x.UnderlyingTransportName + } + return "" +} + +func (x *ClientConfig) GetMaxWriteDelay() int32 { + if x != nil { + return x.MaxWriteDelay + } + return 0 +} + +func (x *ClientConfig) GetMaxRequestSize() int32 { + if x != nil { + return x.MaxRequestSize + } + return 0 +} + +func (x *ClientConfig) GetPollingIntervalInitial() int32 { + if x != nil { + return x.PollingIntervalInitial + } + return 0 +} + +type ServerConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UnderlyingTransportSetting *anypb.Any `protobuf:"bytes,1,opt,name=underlying_transport_setting,json=underlyingTransportSetting,proto3" json:"underlying_transport_setting,omitempty"` + UnderlyingTransportName string `protobuf:"bytes,2,opt,name=underlying_transport_name,json=underlyingTransportName,proto3" json:"underlying_transport_name,omitempty"` + MaxWriteSize int32 `protobuf:"varint,3,opt,name=max_write_size,json=maxWriteSize,proto3" json:"max_write_size,omitempty"` + MaxWriteDurationMs int32 `protobuf:"varint,4,opt,name=max_write_duration_ms,json=maxWriteDurationMs,proto3" json:"max_write_duration_ms,omitempty"` + MaxSimultaneousWriteConnection int32 `protobuf:"varint,5,opt,name=max_simultaneous_write_connection,json=maxSimultaneousWriteConnection,proto3" json:"max_simultaneous_write_connection,omitempty"` + PacketWritingBuffer int32 `protobuf:"varint,6,opt,name=packet_writing_buffer,json=packetWritingBuffer,proto3" json:"packet_writing_buffer,omitempty"` +} + +func (x *ServerConfig) Reset() { + *x = ServerConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_request_assembler_packetconn_packetConn_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerConfig) ProtoMessage() {} + +func (x *ServerConfig) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_request_assembler_packetconn_packetConn_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 ServerConfig.ProtoReflect.Descriptor instead. +func (*ServerConfig) Descriptor() ([]byte, []int) { + return file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescGZIP(), []int{1} +} + +func (x *ServerConfig) GetUnderlyingTransportSetting() *anypb.Any { + if x != nil { + return x.UnderlyingTransportSetting + } + return nil +} + +func (x *ServerConfig) GetUnderlyingTransportName() string { + if x != nil { + return x.UnderlyingTransportName + } + return "" +} + +func (x *ServerConfig) GetMaxWriteSize() int32 { + if x != nil { + return x.MaxWriteSize + } + return 0 +} + +func (x *ServerConfig) GetMaxWriteDurationMs() int32 { + if x != nil { + return x.MaxWriteDurationMs + } + return 0 +} + +func (x *ServerConfig) GetMaxSimultaneousWriteConnection() int32 { + if x != nil { + return x.MaxSimultaneousWriteConnection + } + return 0 +} + +func (x *ServerConfig) GetPacketWritingBuffer() int32 { + if x != nil { + return x.PacketWritingBuffer + } + return 0 +} + +var File_transport_internet_request_assembler_packetconn_packetConn_proto protoreflect.FileDescriptor + +var file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDesc = []byte{ + 0x0a, 0x40, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x73, 0x73, + 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x63, 0x6f, 0x6e, + 0x6e, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x3a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, + 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, + 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x6e, 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, + 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe4, 0x02, 0x0a, 0x0c, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x56, 0x0a, 0x1c, + 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x1a, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, + 0x79, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3a, 0x0a, 0x19, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, + 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, + 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x53, 0x69, + 0x7a, 0x65, 0x12, 0x38, 0x0a, 0x18, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3a, 0x34, 0x82, 0xb5, + 0x18, 0x30, 0x0a, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2e, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x63, 0x6f, + 0x6e, 0x6e, 0x22, 0xb0, 0x03, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x56, 0x0a, 0x1c, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, + 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, + 0x1a, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3a, 0x0a, 0x19, 0x75, + 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, + 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0c, 0x6d, 0x61, 0x78, 0x57, 0x72, 0x69, 0x74, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x31, 0x0a, + 0x15, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x6d, 0x61, + 0x78, 0x57, 0x72, 0x69, 0x74, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, + 0x12, 0x49, 0x0a, 0x21, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x74, 0x61, 0x6e, + 0x65, 0x6f, 0x75, 0x73, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1e, 0x6d, 0x61, 0x78, + 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x74, 0x61, 0x6e, 0x65, 0x6f, 0x75, 0x73, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x70, + 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x75, + 0x66, 0x66, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x57, 0x72, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x3a, + 0x34, 0x82, 0xb5, 0x18, 0x30, 0x0a, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x63, 0x6f, 0x6e, 0x6e, 0x42, 0xcf, 0x01, 0x0a, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, + 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x6e, 0x50, 0x01, 0x5a, 0x4e, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2f, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x6e, 0xaa, 0x02, 0x3a, 0x56, 0x32, 0x52, + 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescOnce sync.Once + file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescData = file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDesc +) + +func file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescGZIP() []byte { + file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescOnce.Do(func() { + file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescData) + }) + return file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDescData +} + +var file_transport_internet_request_assembler_packetconn_packetConn_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_transport_internet_request_assembler_packetconn_packetConn_proto_goTypes = []any{ + (*ClientConfig)(nil), // 0: v2ray.core.transport.internet.request.assembler.packetconn.ClientConfig + (*ServerConfig)(nil), // 1: v2ray.core.transport.internet.request.assembler.packetconn.ServerConfig + (*anypb.Any)(nil), // 2: google.protobuf.Any +} +var file_transport_internet_request_assembler_packetconn_packetConn_proto_depIdxs = []int32{ + 2, // 0: v2ray.core.transport.internet.request.assembler.packetconn.ClientConfig.underlying_transport_setting:type_name -> google.protobuf.Any + 2, // 1: v2ray.core.transport.internet.request.assembler.packetconn.ServerConfig.underlying_transport_setting:type_name -> google.protobuf.Any + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_transport_internet_request_assembler_packetconn_packetConn_proto_init() } +func file_transport_internet_request_assembler_packetconn_packetConn_proto_init() { + if File_transport_internet_request_assembler_packetconn_packetConn_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_request_assembler_packetconn_packetConn_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*ClientConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_transport_internet_request_assembler_packetconn_packetConn_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*ServerConfig); 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_request_assembler_packetconn_packetConn_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_request_assembler_packetconn_packetConn_proto_goTypes, + DependencyIndexes: file_transport_internet_request_assembler_packetconn_packetConn_proto_depIdxs, + MessageInfos: file_transport_internet_request_assembler_packetconn_packetConn_proto_msgTypes, + }.Build() + File_transport_internet_request_assembler_packetconn_packetConn_proto = out.File + file_transport_internet_request_assembler_packetconn_packetConn_proto_rawDesc = nil + file_transport_internet_request_assembler_packetconn_packetConn_proto_goTypes = nil + file_transport_internet_request_assembler_packetconn_packetConn_proto_depIdxs = nil +} diff --git a/transport/internet/request/assembler/packetconn/packetConn.proto b/transport/internet/request/assembler/packetconn/packetConn.proto new file mode 100644 index 000000000..fd4d6cc68 --- /dev/null +++ b/transport/internet/request/assembler/packetconn/packetConn.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package v2ray.core.transport.internet.request.assembler.packetconn; +option csharp_namespace = "V2Ray.Core.Transport.Internet.Request.Assembler.Packetconn"; +option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembler/packetconn"; +option java_package = "com.v2ray.core.transport.internet.request.assembler.packetconn"; +option java_multiple_files = true; + +import "common/protoext/extensions.proto"; +import "google/protobuf/any.proto"; + +message ClientConfig { + option (v2ray.core.common.protoext.message_opt).type = "transport.request.assembler.client"; + option (v2ray.core.common.protoext.message_opt).short_name = "packetconn"; + + google.protobuf.Any underlying_transport_setting = 1; + string underlying_transport_name = 2; + + int32 max_write_delay = 3; + int32 max_request_size = 4; + int32 polling_interval_initial = 5; +} + +message ServerConfig { + option (v2ray.core.common.protoext.message_opt).type = "transport.request.assembler.server"; + option (v2ray.core.common.protoext.message_opt).short_name = "packetconn"; + + google.protobuf.Any underlying_transport_setting = 1; + string underlying_transport_name = 2; + + int32 max_write_size = 3; + int32 max_write_duration_ms = 4; + int32 max_simultaneous_write_connection = 5; + int32 packet_writing_buffer = 6; +} \ No newline at end of file diff --git a/transport/internet/request/assembler/packetconn/packetbundle.go b/transport/internet/request/assembler/packetconn/packetbundle.go new file mode 100644 index 000000000..976d3036b --- /dev/null +++ b/transport/internet/request/assembler/packetconn/packetbundle.go @@ -0,0 +1,48 @@ +package packetconn + +import ( + "encoding/binary" + "io" +) + +func NewPacketBundle() PacketBundle { + return &packetBundle{} +} + +type packetBundle struct{} + +func (p *packetBundle) Overhead() int { + return 2 +} + +func (p *packetBundle) WriteToBundle(b []byte, writer io.Writer) (err error) { + err = binary.Write(writer, binary.BigEndian, uint16(len(b))) + if err != nil { + return + } + _, err = writer.Write(b) + return +} + +func (p *packetBundle) ReadFromBundle(writer io.Reader) (b []byte, err error) { + var length uint16 + err = binary.Read(writer, binary.BigEndian, &length) + if err != nil { + return + } + b = make([]byte, length) + n, err := io.ReadFull(writer, b) + if err != nil { + return + } + if n != int(length) { + return nil, io.ErrUnexpectedEOF + } + return +} + +type PacketBundle interface { + Overhead() int + WriteToBundle(b []byte, writer io.Writer) (err error) + ReadFromBundle(writer io.Reader) (b []byte, err error) +} diff --git a/transport/internet/request/assembler/packetconn/packetconn.go b/transport/internet/request/assembler/packetconn/packetconn.go new file mode 100644 index 000000000..01b6df44d --- /dev/null +++ b/transport/internet/request/assembler/packetconn/packetconn.go @@ -0,0 +1,3 @@ +package packetconn + +//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen diff --git a/transport/internet/request/assembler/packetconn/req2packet.go b/transport/internet/request/assembler/packetconn/req2packet.go new file mode 100644 index 000000000..2ce417148 --- /dev/null +++ b/transport/internet/request/assembler/packetconn/req2packet.go @@ -0,0 +1,429 @@ +package packetconn + +import ( + "bytes" + "context" + "crypto/rand" + "io" + "time" + + "github.com/golang-collections/go-datastructures/queue" + + "github.com/v2fly/v2ray-core/v5/transport/internet/request" +) + +func newRequestToPacketConnClient(ctx context.Context, config *ClientConfig) (*requestToPacketConnClient, error) { //nolint: unparam + return &requestToPacketConnClient{ctx: ctx, config: config}, nil +} + +type requestToPacketConnClient struct { + assembly request.TransportClientAssembly + ctx context.Context + config *ClientConfig +} + +func (r *requestToPacketConnClient) OnTransportClientAssemblyReady(assembly request.TransportClientAssembly) { + r.assembly = assembly +} + +func (r *requestToPacketConnClient) Dial() (io.ReadWriteCloser, error) { + sessionID := make([]byte, 16) + _, err := rand.Read(sessionID) + if err != nil { + return nil, err + } + ctxWithCancel, cancel := context.WithCancel(r.ctx) + + clientSess := &requestToPacketConnClientSession{ + sessionID: sessionID, + currentPollingInterval: int(r.config.PollingIntervalInitial), + maxRequestSize: int(r.config.MaxRequestSize), + maxWriteDelay: int(r.config.MaxWriteDelay), + assembly: r.assembly, + writerChan: make(chan []byte, 256), + readerChan: make(chan []byte, 256), + ctx: ctxWithCancel, + finish: cancel, + } + go clientSess.keepRunning() + return clientSess, nil +} + +type requestToPacketConnClientSession struct { + sessionID []byte + currentPollingInterval int + + maxRequestSize int + maxWriteDelay int + + assembly request.TransportClientAssembly + writerChan chan []byte + readerChan chan []byte + ctx context.Context + finish func() + nextWrite []byte +} + +func (r *requestToPacketConnClientSession) keepRunning() { + for r.ctx.Err() == nil { + r.runOnce() + } +} + +func (r *requestToPacketConnClientSession) runOnce() { + requestBody := bytes.NewBuffer(nil) + waitTimer := time.NewTimer(time.Duration(r.currentPollingInterval) * time.Millisecond) + var seenPacket bool + packetBundler := NewPacketBundle() +copyFromChan: + for { + select { + case <-r.ctx.Done(): + return + case <-waitTimer.C: + break copyFromChan + case packet := <-r.writerChan: + if !seenPacket { + seenPacket = true + waitTimer.Stop() + waitTimer.Reset(time.Duration(r.maxWriteDelay) * time.Millisecond) + } + sizeOffset := packetBundler.Overhead() + len(packet) + if requestBody.Len()+sizeOffset > r.maxRequestSize { + r.nextWrite = packet + break copyFromChan + } + err := packetBundler.WriteToBundle(packet, requestBody) + if err != nil { + newError("failed to write to bundle").Base(err).WriteToLog() + } + } + } + waitTimer.Stop() + go func() { + reader, writer := io.Pipe() + streamingRespOpt := &pipedStreamingRespOption{writer} + go func() { + for { + if packet, err := packetBundler.ReadFromBundle(reader); err == nil { + r.readerChan <- packet + } else { + return + } + } + }() + resp, err := r.assembly.Tripper().RoundTrip(r.ctx, request.Request{Data: requestBody.Bytes(), ConnectionTag: r.sessionID}, + streamingRespOpt) + if err != nil { + newError("failed to roundtrip").Base(err).WriteToLog() + if r.ctx.Err() != nil { + return + } + } + if resp.Data != nil && len(resp.Data) != 0 { + respReader := bytes.NewReader(resp.Data) + for respReader.Len() != 0 { + packet, err := packetBundler.ReadFromBundle(respReader) + if err != nil { + newError("failed to read from bundle").Base(err).WriteToLog() + if r.ctx.Err() != nil { + return + } + } + r.readerChan <- packet + } + } + }() +} + +type pipedStreamingRespOption struct { + writer *io.PipeWriter +} + +func (p *pipedStreamingRespOption) RoundTripperOption() { +} + +func (p *pipedStreamingRespOption) GetResponseWriter() io.Writer { + return p.writer +} + +func (r *requestToPacketConnClientSession) Write(p []byte) (n int, err error) { + buf := make([]byte, len(p)) + copy(buf, p) + select { + case <-r.ctx.Done(): + return 0, r.ctx.Err() + case r.writerChan <- buf: + return len(p), nil + } +} + +func (r *requestToPacketConnClientSession) Read(p []byte) (n int, err error) { + select { + case <-r.ctx.Done(): + return 0, r.ctx.Err() + case buf := <-r.readerChan: + copy(p, buf) + return len(buf), nil + } +} + +func (r *requestToPacketConnClientSession) Close() error { + r.finish() + return nil +} + +func newRequestToPacketConnServer(ctx context.Context, config *ServerConfig) *requestToPacketConnServer { + return &requestToPacketConnServer{ + sessionMap: make(map[string]*requestToPacketConnServerSession), + ctx: ctx, + config: config, + } +} + +type requestToPacketConnServer struct { + packetSessionReceiver request.SessionReceiver + + sessionMap map[string]*requestToPacketConnServerSession + + ctx context.Context + config *ServerConfig +} + +func (r *requestToPacketConnServer) onSessionReceiverReady(sessrecv request.SessionReceiver) { + r.packetSessionReceiver = sessrecv +} + +func (r *requestToPacketConnServer) OnRoundTrip(ctx context.Context, req request.Request, + opts ...request.RoundTripperOption, +) (resp request.Response, err error) { + SessionID := req.ConnectionTag + if SessionID == nil { + return request.Response{}, newError("nil session id") + } + sessionID := string(SessionID) + session, found := r.sessionMap[sessionID] + if !found { + ctxWithFinish, finish := context.WithCancel(ctx) + session = &requestToPacketConnServerSession{ + SessionID: SessionID, + writingConnectionQueue: queue.New(64), + writerChan: make(chan []byte, int(r.config.PacketWritingBuffer)), + readerChan: make(chan []byte, 256), + ctx: ctxWithFinish, + finish: finish, + server: r, + maxWriteSize: int(r.config.MaxWriteSize), + maxWriteDuration: int(r.config.MaxWriteDurationMs), + maxSimultaneousWriteConnection: int(r.config.MaxSimultaneousWriteConnection), + } + r.sessionMap[sessionID] = session + err = r.packetSessionReceiver.OnNewSession(ctx, session) + } + if err != nil { + return request.Response{}, err + } + return session.OnRoundTrip(ctx, req, opts...) +} + +func (r *requestToPacketConnServer) removeSessionID(sessionID []byte) { + delete(r.sessionMap, string(sessionID)) +} + +type requestToPacketConnServerSession struct { + SessionID []byte + + writingConnectionQueue *queue.Queue + + writerChan chan []byte + readerChan chan []byte + ctx context.Context + finish func() + server *requestToPacketConnServer + + maxWriteSize int + maxWriteDuration int + maxSimultaneousWriteConnection int +} + +func (r *requestToPacketConnServerSession) Read(p []byte) (n int, err error) { + select { + case <-r.ctx.Done(): + return 0, r.ctx.Err() + case buf := <-r.readerChan: + copy(p, buf) + return len(buf), nil + } +} + +var debugStats struct { + packetWritten int + packetDropped int +} + +/* +var _ = func() bool { + go func() { + for { + time.Sleep(time.Second) + newError("packet written: ", debugStats.packetWritten, " packet dropped: ", debugStats.packetDropped).WriteToLog() + } + }() + return true +}()*/ + +func (r *requestToPacketConnServerSession) Write(p []byte) (n int, err error) { + buf := make([]byte, len(p)) + copy(buf, p) + select { + case <-r.ctx.Done(): + return 0, r.ctx.Err() + case r.writerChan <- buf: + debugStats.packetWritten++ + return len(p), nil + default: // This write will be called from global listener's routine, it must not block + debugStats.packetDropped++ + return len(p), nil + } +} + +func (r *requestToPacketConnServerSession) Close() error { + r.server.removeSessionID(r.SessionID) + r.finish() + return nil +} + +type writingConnection struct { + focus func() + finish func() + finishCtx context.Context +} + +func (r *requestToPacketConnServerSession) OnRoundTrip(ctx context.Context, req request.Request, + opts ...request.RoundTripperOption, +) (resp request.Response, err error) { + // TODO: fix connection graceful close + var streamingRespWriter io.Writer + var streamingRespWriterFlusher request.OptionSupportsStreamingResponseExtensionFlusher + for _, opt := range opts { + if streamingRespOpt, ok := opt.(request.OptionSupportsStreamingResponse); ok { + streamingRespWriter = streamingRespOpt.GetResponseWriter() + if streamingRespWriterFlusherOpt, ok := opt.(request.OptionSupportsStreamingResponseExtensionFlusher); ok { + streamingRespWriterFlusher = streamingRespWriterFlusherOpt + } + } + } + packetBundler := NewPacketBundle() + reqReader := bytes.NewReader(req.Data) + for reqReader.Len() != 0 { + packet, err := packetBundler.ReadFromBundle(reqReader) + if err != nil { + err = newError("failed to read from bundle").Base(err) + return request.Response{}, err + } + r.readerChan <- packet + } + onFocusCtx, focus := context.WithCancel(ctx) + onFinishCtx, finish := context.WithCancel(ctx) + r.writingConnectionQueue.Put(&writingConnection{ + focus: focus, + finish: finish, + finishCtx: onFinishCtx, + }) + + amountToEnd := r.writingConnectionQueue.Len() - int64(r.maxSimultaneousWriteConnection) + for amountToEnd > 0 { + { + _, _ = r.writingConnectionQueue.TakeUntil(func(i interface{}) bool { + i.(*writingConnection).finish() + amountToEnd-- + return amountToEnd > 0 + }) + } + } + + { + _, _ = r.writingConnectionQueue.TakeUntil(func(i interface{}) bool { + i.(*writingConnection).focus() + return false + }) + } + + bufferedRespWriter := bytes.NewBuffer(nil) + finishWrite := func() { + resp.Data = bufferedRespWriter.Bytes() + { + _, _ = r.writingConnectionQueue.TakeUntil(func(i interface{}) bool { + i.(*writingConnection).focus() + if i.(*writingConnection).finishCtx.Err() != nil { //nolint: gosimple + return true + } + return false + }) + } + } + + progressiveSend := streamingRespWriter != nil + var respWriter io.Writer + if progressiveSend { + respWriter = streamingRespWriter + } else { + respWriter = bufferedRespWriter + } + + var bytesSent int + onReceivePacket := func(packet []byte) bool { + bytesSent += len(packet) + packetBundler.Overhead() + err := packetBundler.WriteToBundle(packet, respWriter) + if err != nil { + newError("failed to write to bundle").Base(err).WriteToLog() + } + if streamingRespWriterFlusher != nil { + streamingRespWriterFlusher.Flush() + } + if bytesSent >= r.maxWriteSize { + return false + } + return true + } + + finishWriteTimer := time.NewTimer(time.Millisecond * time.Duration(r.maxWriteDuration)) + + if !progressiveSend { + select { + case <-onFocusCtx.Done(): + case <-onFinishCtx.Done(): + finishWrite() + return resp, nil + } + } else { + select { + case <-onFinishCtx.Done(): + finishWrite() + return resp, nil + default: + } + } + firstRead := true + for { + select { + case <-onFinishCtx.Done(): + finishWrite() + finishWriteTimer.Stop() + return resp, nil + case packet := <-r.writerChan: + keepSending := onReceivePacket(packet) + if firstRead { + firstRead = false + } + if !keepSending { + finishWrite() + finishWriteTimer.Stop() + return resp, nil + } + case <-finishWriteTimer.C: + finishWrite() + return resp, nil + } + } +} diff --git a/transport/internet/request/assembler/packetconn/udpassembler.go b/transport/internet/request/assembler/packetconn/udpassembler.go new file mode 100644 index 000000000..19ac09108 --- /dev/null +++ b/transport/internet/request/assembler/packetconn/udpassembler.go @@ -0,0 +1,78 @@ +package packetconn + +import ( + "golang.org/x/net/context" + + "github.com/v2fly/v2ray-core/v5/common" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/net" + "github.com/v2fly/v2ray-core/v5/common/serial" + "github.com/v2fly/v2ray-core/v5/transport/internet" +) + +type wrappedTransportEnvironment struct { + environment.TransportEnvironment + client *requestToPacketConnClient + server *requestToPacketConnServer +} + +func (w *wrappedTransportEnvironment) Listen(ctx context.Context, addr net.Addr, sockopt *internet.SocketConfig) (net.Listener, error) { + return nil, newError("not implemented") +} + +func (w *wrappedTransportEnvironment) ListenPacket(ctx context.Context, addr net.Addr, sockopt *internet.SocketConfig) (net.PacketConn, error) { + packetConn := newWrappedPacketConn(ctx) + w.server.onSessionReceiverReady(packetConn) + return packetConn, nil +} + +func (w *wrappedTransportEnvironment) Dial(ctx context.Context, source net.Address, destination net.Destination, sockopt *internet.SocketConfig) (net.Conn, error) { + session, err := w.client.Dial() + if err != nil { + return nil, err + } + return newWrappedConn(session), nil +} + +func (w *wrappedTransportEnvironment) Dialer() internet.SystemDialer { + return w +} + +func (w *wrappedTransportEnvironment) Listener() internet.SystemListener { + return w +} + +func newUDPAssemblerServerFromConfig(ctx context.Context, config *ServerConfig) (*udpAssemblerServer, error) { + instance, err := serial.GetInstanceOf(config.UnderlyingTransportSetting) + if err != nil { + return nil, newError("failed to get instance of underlying transport").Base(err).AtError() + } + memcfg := &internet.MemoryStreamConfig{ProtocolName: config.UnderlyingTransportName, ProtocolSettings: instance} + return newUDPAssemblerServer(ctx, config, memcfg), nil +} + +func newUDPAssemblerClientFromConfig(ctx context.Context, config *ClientConfig) (*udpAssemblerClient, error) { + instance, err := serial.GetInstanceOf(config.UnderlyingTransportSetting) + if err != nil { + return nil, newError("failed to get instance of underlying transport").Base(err).AtError() + } + memcfg := &internet.MemoryStreamConfig{ProtocolName: config.UnderlyingTransportName, ProtocolSettings: instance} + return newUDPAssemblerClient(ctx, config, memcfg), nil +} + +func init() { + common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + serverConfig, ok := config.(*ServerConfig) + if !ok { + return nil, newError("not a ServerConfig") + } + return newUDPAssemblerServerFromConfig(ctx, serverConfig) + })) + 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 newUDPAssemblerClientFromConfig(ctx, clientConfig) + })) +} diff --git a/transport/internet/request/assembler/packetconn/udpassemblerClient.go b/transport/internet/request/assembler/packetconn/udpassemblerClient.go new file mode 100644 index 000000000..c85802ab4 --- /dev/null +++ b/transport/internet/request/assembler/packetconn/udpassemblerClient.go @@ -0,0 +1,81 @@ +package packetconn + +import ( + "io" + gonet "net" + "sync" + "time" + + "golang.org/x/net/context" + + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/v2fly/v2ray-core/v5/common/net" + "github.com/v2fly/v2ray-core/v5/transport/internet" + "github.com/v2fly/v2ray-core/v5/transport/internet/request" +) + +type udpAssemblerClient struct { + ctx context.Context + streamSettings *internet.MemoryStreamConfig + assembly request.TransportClientAssembly + req2connc *requestToPacketConnClient +} + +func (u *udpAssemblerClient) NewSession(ctx context.Context, opts ...request.SessionOption) (request.Session, error) { + return u.dial(net.Destination{}) +} + +func (u *udpAssemblerClient) OnTransportClientAssemblyReady(assembly request.TransportClientAssembly) { + u.assembly = assembly + u.req2connc.OnTransportClientAssemblyReady(assembly) +} + +func newWrappedConn(in io.ReadWriteCloser) net.Conn { + return wrappedConn{in} +} + +type wrappedConn struct { + io.ReadWriteCloser +} + +func (w wrappedConn) LocalAddr() gonet.Addr { + return nil +} + +func (w wrappedConn) RemoteAddr() gonet.Addr { + return nil +} + +func (w wrappedConn) SetDeadline(t time.Time) error { + return nil +} + +func (w wrappedConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (w wrappedConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func newWrappedPacketConn(ctx context.Context) *wrappedPacketConn { + ctxWithCancel, cancel := context.WithCancel(ctx) + return &wrappedPacketConn{ + conn: make(map[string]*serverSession), + readChan: make(chan packet, 16), ctx: ctxWithCancel, finish: cancel, connLock: &sync.Mutex{}, + } +} + +func newUDPAssemblerClient(ctx context.Context, config *ClientConfig, streamSettings *internet.MemoryStreamConfig) *udpAssemblerClient { + transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment) + transportEnvironmentWrapped := &wrappedTransportEnvironment{TransportEnvironment: transportEnvironment} + transportEnvironmentWrapped.client, _ = newRequestToPacketConnClient(ctx, config) + wrappedContext := envctx.ContextWithEnvironment(ctx, transportEnvironmentWrapped) + return &udpAssemblerClient{ctx: wrappedContext, streamSettings: streamSettings, req2connc: transportEnvironmentWrapped.client} +} + +func (u *udpAssemblerClient) dial(dest net.Destination) (internet.Connection, error) { + _ = dest + return internet.Dial(u.ctx, net.TCPDestination(net.LocalHostIP, 0), u.streamSettings) +} diff --git a/transport/internet/request/assembler/packetconn/udpassemblerServer.go b/transport/internet/request/assembler/packetconn/udpassemblerServer.go new file mode 100644 index 000000000..a8135f3d9 --- /dev/null +++ b/transport/internet/request/assembler/packetconn/udpassemblerServer.go @@ -0,0 +1,166 @@ +package packetconn + +import ( + "crypto/rand" + "io" + "net" + "sync" + "time" + + "golang.org/x/net/context" + + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + net2 "github.com/v2fly/v2ray-core/v5/common/net" + "github.com/v2fly/v2ray-core/v5/transport/internet" + "github.com/v2fly/v2ray-core/v5/transport/internet/request" +) + +type packet struct { + addr string + data []byte +} + +type wrappedPacketConn struct { + connLock *sync.Mutex + conn map[string]*serverSession + + readChan chan packet + + ctx context.Context + finish func() +} + +func (w *wrappedPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + select { + case pack := <-w.readChan: + n := copy(p, pack.data) + if n < len(pack.data) { + return n, nil, io.ErrShortBuffer + } + return n, &net.UDPAddr{IP: net2.IP(pack.addr)}, nil + case <-w.ctx.Done(): + return 0, nil, w.ctx.Err() + } +} + +func (w *wrappedPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + w.connLock.Lock() + conn := w.conn[string(addr.(*net.UDPAddr).IP)] + w.connLock.Unlock() + return conn.Write(p) +} + +func (w *wrappedPacketConn) Close() error { + w.finish() + return nil +} + +func (w *wrappedPacketConn) LocalAddr() net.Addr { + return nil +} + +func (w *wrappedPacketConn) SetDeadline(t time.Time) error { + return nil +} + +func (w *wrappedPacketConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (w *wrappedPacketConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func (w wrappedPacketConn) OnNewSession(ctx context.Context, sess request.Session, opts ...request.SessionOption) error { + imaginaryAddr := net2.UDPAddr{ + IP: net2.AnyIPv6.IP(), + Port: 0, + } + rand.Read([]byte(imaginaryAddr.IP)) + session := newServerSession(ctx, sess, string(imaginaryAddr.IP), &w) + w.connLock.Lock() + w.conn[string(imaginaryAddr.IP)] = session + w.connLock.Unlock() + session.start() + return nil +} + +func newServerSession(ctx context.Context, sess request.Session, name string, listener *wrappedPacketConn) *serverSession { + _ = ctx + return &serverSession{session: sess, name: name, listener: listener} +} + +type serverSession struct { + name string + session request.Session + listener *wrappedPacketConn +} + +func (s *serverSession) start() { + go func() { + for { + select { + case <-s.listener.ctx.Done(): + return + default: + buf := make([]byte, 2000) + n, err := s.session.Read(buf) + if err != nil || n > 2000 { + return + } + s.listener.readChan <- packet{s.name, buf[:n]} + } + } + }() +} + +func (s *serverSession) Write(p []byte) (int, error) { + return s.session.Write(p) +} + +type udpAssemblerServer struct { + ctx context.Context + streamSettings *internet.MemoryStreamConfig + assembly request.TransportServerAssembly + req2packs *requestToPacketConnServer + listener internet.Listener +} + +func (u *udpAssemblerServer) Start() error { + listener, err := u.listen(net2.LocalHostIP, 0) + if err != nil { + return newError("failed to listen").Base(err).AtError() + } + u.listener = listener + return nil +} + +func (u *udpAssemblerServer) Close() error { + return u.listener.Close() +} + +func (u *udpAssemblerServer) OnRoundTrip(ctx context.Context, req request.Request, opts ...request.RoundTripperOption) (resp request.Response, err error) { + return u.req2packs.OnRoundTrip(ctx, req, opts...) +} + +func (u *udpAssemblerServer) OnTransportServerAssemblyReady(assembly request.TransportServerAssembly) { + u.assembly = assembly +} + +func newUDPAssemblerServer(ctx context.Context, config *ServerConfig, streamSettings *internet.MemoryStreamConfig) *udpAssemblerServer { + transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment) + transportEnvironmentWrapped := &wrappedTransportEnvironment{TransportEnvironment: transportEnvironment} + transportEnvironmentWrapped.server = newRequestToPacketConnServer(ctx, config) + wrappedContext := envctx.ContextWithEnvironment(ctx, transportEnvironmentWrapped) + return &udpAssemblerServer{ctx: wrappedContext, streamSettings: streamSettings, req2packs: transportEnvironmentWrapped.server} +} + +func (u *udpAssemblerServer) listen(address net2.Address, port net2.Port) (internet.Listener, error) { + return internet.ListenTCP(u.ctx, address, port, u.streamSettings, func(connection internet.Connection) { + err := u.assembly.SessionReceiver().OnNewSession(u.ctx, connection) + if err != nil { + newError("failed to handle new session").Base(err).WriteToLog() + } + }) +} diff --git a/transport/internet/request/assembly/hub.go b/transport/internet/request/assembly/hub.go index 7a6fc404f..8e9e21ef3 100644 --- a/transport/internet/request/assembly/hub.go +++ b/transport/internet/request/assembly/hub.go @@ -36,6 +36,11 @@ func (s server) Close() error { if err := s.tripper.Close(); err != nil { return newError("failed to close tripper").Base(err) } + if runnableAssembler, ok := s.assembler.(common.Runnable); ok { + if err := runnableAssembler.Close(); err != nil { + return newError("failed to close assembler").Base(err) + } + } return nil } @@ -127,6 +132,12 @@ func listenRequest(ctx context.Context, address net.Address, port net.Port, stre return nil, newError("failed to start tripper").Base(err) } + if runnableAssembler, ok := serverAssembly.assembler.(common.Runnable); ok { + if err := runnableAssembler.Start(); err != nil { + return nil, newError("failed to start assembler").Base(err) + } + } + return serverAssembly, nil } diff --git a/transport/internet/request/roundtripper.go b/transport/internet/request/roundtripper.go index abb8f2f89..c921d1be7 100644 --- a/transport/internet/request/roundtripper.go +++ b/transport/internet/request/roundtripper.go @@ -2,6 +2,7 @@ package request import ( "context" + "io" "github.com/v2fly/v2ray-core/v5/common" ) @@ -36,3 +37,12 @@ type Request struct { type Response struct { Data []byte } + +type OptionSupportsStreamingResponse interface { + RoundTripperOption + GetResponseWriter() io.Writer +} + +type OptionSupportsStreamingResponseExtensionFlusher interface { + Flush() +} diff --git a/transport/internet/request/roundtripper/httprt/config.pb.go b/transport/internet/request/roundtripper/httprt/config.pb.go index a93feb0a9..f2950eec2 100644 --- a/transport/internet/request/roundtripper/httprt/config.pb.go +++ b/transport/internet/request/roundtripper/httprt/config.pb.go @@ -20,7 +20,9 @@ type ClientConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Http *HTTPConfig `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"` + Http *HTTPConfig `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"` + AllowHttp bool `protobuf:"varint,2,opt,name=allow_http,json=allowHttp,proto3" json:"allow_http,omitempty"` + H2PoolSize int32 `protobuf:"varint,3,opt,name=h2_pool_size,json=h2PoolSize,proto3" json:"h2_pool_size,omitempty"` } func (x *ClientConfig) Reset() { @@ -62,6 +64,20 @@ func (x *ClientConfig) GetHttp() *HTTPConfig { return nil } +func (x *ClientConfig) GetAllowHttp() bool { + if x != nil { + return x.AllowHttp + } + return false +} + +func (x *ClientConfig) GetH2PoolSize() int32 { + if x != nil { + return x.H2PoolSize + } + return 0 +} + type ServerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -184,48 +200,52 @@ var file_transport_internet_request_roundtripper_httprt_config_proto_rawDesc = [ 0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 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, 0x9e, 0x01, 0x0a, 0x0c, 0x43, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdf, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x33, 0x82, 0xb5, 0x18, 0x2f, 0x0a, 0x25, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x12, 0x06, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x22, 0xd5, 0x01, 0x0a, 0x0c, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x04, - 0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x32, 0x72, + 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, + 0x68, 0x74, 0x74, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x48, 0x74, 0x74, 0x70, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x32, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x68, 0x32, 0x50, + 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x3a, 0x33, 0x82, 0xb5, 0x18, 0x2f, 0x0a, 0x25, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x12, 0x06, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x22, 0xd5, 0x01, 0x0a, + 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, + 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x32, + 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, + 0x2e, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x5f, 0x64, + 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6e, 0x6f, 0x44, 0x65, 0x63, + 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x67, 0x3a, + 0x33, 0x82, 0xb5, 0x18, 0x2f, 0x0a, 0x25, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, + 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x06, 0x68, 0x74, + 0x74, 0x70, 0x72, 0x74, 0x22, 0x3e, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x72, 0x6c, 0x50, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x72, 0x6c, 0x50, 0x72, + 0x65, 0x66, 0x69, 0x78, 0x42, 0xcc, 0x01, 0x0a, 0x3d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, - 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x5f, 0x64, 0x65, - 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6e, 0x6f, 0x44, 0x65, 0x63, 0x6f, - 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x67, 0x3a, 0x33, - 0x82, 0xb5, 0x18, 0x2f, 0x0a, 0x25, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, - 0x70, 0x70, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x06, 0x68, 0x74, 0x74, - 0x70, 0x72, 0x74, 0x22, 0x3e, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x72, 0x6c, 0x50, 0x72, 0x65, 0x66, - 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x72, 0x6c, 0x50, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x42, 0xcc, 0x01, 0x0a, 0x3d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, - 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68, - 0x74, 0x74, 0x70, 0x72, 0x74, 0x50, 0x01, 0x5a, 0x4d, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, - 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2f, - 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0xaa, 0x02, 0x39, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, - 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68, 0x74, 0x74, 0x70, - 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x50, 0x01, 0x5a, 0x4d, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, + 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, + 0x2f, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0xaa, 0x02, 0x39, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, + 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68, 0x74, 0x74, + 0x70, 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/request/roundtripper/httprt/config.proto b/transport/internet/request/roundtripper/httprt/config.proto index ac2df25d6..1b750eff2 100644 --- a/transport/internet/request/roundtripper/httprt/config.proto +++ b/transport/internet/request/roundtripper/httprt/config.proto @@ -13,6 +13,8 @@ message ClientConfig { option (v2ray.core.common.protoext.message_opt).short_name = "httprt"; HTTPConfig http = 1; + bool allow_http = 2; + int32 h2_pool_size = 3; } message ServerConfig { diff --git a/transport/internet/request/roundtripper/httprt/httprt.go b/transport/internet/request/roundtripper/httprt/httprt.go index 16035a460..e98d80daf 100644 --- a/transport/internet/request/roundtripper/httprt/httprt.go +++ b/transport/internet/request/roundtripper/httprt/httprt.go @@ -40,31 +40,55 @@ func (h *httpTripperClient) OnTransportClientAssemblyReady(assembly request.Tran } func (h *httpTripperClient) RoundTrip(ctx context.Context, req request.Request, opts ...request.RoundTripperOption) (resp request.Response, err error) { + var streamingWriter io.Writer + for _, v := range opts { + if streamingResp, ok := v.(request.OptionSupportsStreamingResponse); ok { + streamingWriter = streamingResp.GetResponseWriter() + } + } if h.httpRTT == nil { - h.httpRTT = transportcommon.NewALPNAwareHTTPRoundTripper(ctx, func(ctx context.Context, addr string) (gonet.Conn, error) { + var backDrop http.RoundTripper = unimplementedBackDrop{} + if h.config.AllowHttp { + backDrop = &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (gonet.Conn, error) { + return h.assembly.AutoImplDialer().Dial(ctx) + }, + DialTLSContext: func(_ context.Context, network, addr string) (gonet.Conn, error) { + return nil, newError("unexpected dial of TLS") + }, + } + } + h.httpRTT = transportcommon.NewALPNAwareHTTPRoundTripperWithH2Pool(ctx, func(ctx context.Context, addr string) (gonet.Conn, error) { return h.assembly.AutoImplDialer().Dial(ctx) - }, unimplementedBackDrop{}) + }, backDrop, int(h.config.H2PoolSize)) } connectionTagStr := base64.RawURLEncoding.EncodeToString(req.ConnectionTag) httpRequest, err := http.NewRequest("POST", h.config.Http.UrlPrefix+h.config.Http.Path, bytes.NewReader(req.Data)) if err != nil { - return + return resp, err } httpRequest.Header.Set("X-Session-ID", connectionTagStr) httpResp, err := h.httpRTT.RoundTrip(httpRequest) if err != nil { - return + return resp, err } defer httpResp.Body.Close() - result, err := io.ReadAll(httpResp.Body) - if err != nil { - return + if streamingWriter == nil { + result, err := io.ReadAll(httpResp.Body) + if err != nil { + return request.Response{}, err + } + return request.Response{Data: result}, err } - return request.Response{Data: result}, err + _, err = io.Copy(streamingWriter, httpResp.Body) + if err != nil { + return request.Response{}, newError("unable to copy response").Base(err) + } + return request.Response{}, nil } func newHTTPRoundTripperServer(ctx context.Context, config *ServerConfig) request.RoundTripperServer { @@ -87,6 +111,25 @@ func (h *httpTripperServer) ServeHTTP(writer http.ResponseWriter, r *http.Reques h.onRequest(writer, r) } +type httpRespStreamWriting struct { + resp http.ResponseWriter + used bool +} + +func (h *httpRespStreamWriting) RoundTripperOption() { +} + +func (h *httpRespStreamWriting) GetResponseWriter() io.Writer { + h.used = true + return h.resp +} + +func (h *httpRespStreamWriting) Flush() { + if f, ok := h.resp.(http.Flusher); ok { + f.Flush() + } +} + func (h *httpTripperServer) onRequest(resp http.ResponseWriter, req *http.Request) { tail := req.Header.Get("X-Session-ID") data := []byte(tail) @@ -103,10 +146,16 @@ func (h *httpTripperServer) onRequest(resp http.ResponseWriter, req *http.Reques if err != nil { newError("unable to read body").Base(err).AtInfo().WriteToLog() } - recvResp, err := h.assembly.TripperReceiver().OnRoundTrip(h.ctx, request.Request{Data: body, ConnectionTag: data}) + + streamingRespOption := &httpRespStreamWriting{resp: resp} + recvResp, err := h.assembly.TripperReceiver().OnRoundTrip(h.ctx, request.Request{Data: body, ConnectionTag: data}, + streamingRespOption) if err != nil { newError("unable to process roundtrip").Base(err).AtInfo().WriteToLog() } + if streamingRespOption.used { + return + } _, err = io.Copy(resp, bytes.NewReader(recvResp.Data)) if err != nil { newError("unable to send response").Base(err).AtInfo().WriteToLog() diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 5d3594246..534055d7d 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" + "os" "strings" "sync" "time" @@ -194,6 +195,16 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert return nil } +type alwaysFlushWriter struct { + file *os.File +} + +func (a *alwaysFlushWriter) Write(p []byte) (n int, err error) { + n, err = a.file.Write(p) + a.file.Sync() + return n, err +} + // GetTLSConfig converts this Config into tls.Config. func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { root, err := c.getCertPool() diff --git a/transport/internet/transportcommon/httpDialer.go b/transport/internet/transportcommon/httpDialer.go index aafebc939..4db68f749 100644 --- a/transport/internet/transportcommon/httpDialer.go +++ b/transport/internet/transportcommon/httpDialer.go @@ -17,10 +17,17 @@ import ( type DialerFunc func(ctx context.Context, addr string) (net.Conn, error) -// NewALPNAwareHTTPRoundTripper creates an instance of RoundTripper that dial to remote HTTPS endpoint with -// an alternative version of TLS implementation. func NewALPNAwareHTTPRoundTripper(ctx context.Context, dialer DialerFunc, backdropTransport http.RoundTripper, +) http.RoundTripper { + return NewALPNAwareHTTPRoundTripperWithH2Pool(ctx, dialer, backdropTransport, 1) +} + +// NewALPNAwareHTTPRoundTripperWithH2Pool creates an instance of RoundTripper that dial to remote HTTPS endpoint with +// an alternative version of TLS implementation. +func NewALPNAwareHTTPRoundTripperWithH2Pool(ctx context.Context, dialer DialerFunc, + backdropTransport http.RoundTripper, + h2PoolSize int, ) http.RoundTripper { rtImpl := &alpnAwareHTTPRoundTripperImpl{ connectWithH1: map[string]bool{}, @@ -46,6 +53,8 @@ type alpnAwareHTTPRoundTripperImpl struct { ctx context.Context dialer DialerFunc + + h2PoolSize int } type pendingConnKey struct { @@ -175,10 +184,20 @@ func (r *alpnAwareHTTPRoundTripperImpl) dialTLS(ctx context.Context, addr string } func (r *alpnAwareHTTPRoundTripperImpl) init() { - r.httpsH2Transport = &http2.Transport{ - DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { - return r.dialOrGetTLSWithExpectedALPN(context.Background(), addr, true) - }, + if r.h2PoolSize >= 2 { + r.httpsH2Transport = newH2TransportPool(int64(r.h2PoolSize), func() *http2.Transport { + return &http2.Transport{ + DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + return r.dialOrGetTLSWithExpectedALPN(context.Background(), addr, true) + }, + } + }) + } else { + r.httpsH2Transport = &http2.Transport{ + DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + return r.dialOrGetTLSWithExpectedALPN(context.Background(), addr, true) + }, + } } r.httpsH1Transport = &http.Transport{ DialTLSContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { @@ -220,3 +239,29 @@ func (c *unclaimedConnection) tick() { c.Conn = nil } } + +type h2TransportFactory func() *http2.Transport + +func newH2TransportPool(size int64, h2factory h2TransportFactory) *h2TransportPool { + return &h2TransportPool{ + pool: make([]*http2.Transport, size), + size: size, + h2factory: h2factory, + } +} + +type h2TransportPool struct { + pool []*http2.Transport + h2factory h2TransportFactory + usageCount int64 + size int64 +} + +func (h *h2TransportPool) RoundTrip(request *http.Request) (*http.Response, error) { + currentSlot := h.usageCount % h.size + h.usageCount++ + if h.pool[currentSlot] == nil { + h.pool[currentSlot] = h.h2factory() + } + return h.pool[currentSlot].RoundTrip(request) +} diff --git a/transport/internet/udp/hub.go b/transport/internet/udp/hub.go index a544c26d5..6af9caa97 100644 --- a/transport/internet/udp/hub.go +++ b/transport/internet/udp/hub.go @@ -3,6 +3,9 @@ package udp import ( "context" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" + "github.com/v2fly/v2ray-core/v5/common/buf" "github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/common/protocol/udp" @@ -25,6 +28,7 @@ func HubReceiveOriginalDestination(r bool) HubOption { type Hub struct { conn *net.UDPConn + connPacket net.PacketConn cache chan *udp.Packet capacity int recvOrigDest bool @@ -46,8 +50,9 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe if sockopt != nil && sockopt.ReceiveOriginalDestAddress { hub.recvOrigDest = true } - - udpConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{ + transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment) + listener := transportEnvironment.Listener() + udpConn, err := listener.ListenPacket(ctx, &net.UDPAddr{ IP: address.IP(), Port: int(port), }, sockopt) @@ -55,7 +60,12 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe return nil, err } newError("listening UDP on ", address, ":", port).WriteToLog() - hub.conn = udpConn.(*net.UDPConn) + if udpConnDirect, ok := udpConn.(*net.UDPConn); ok { + hub.conn = udpConnDirect + } else { + hub.connPacket = udpConn + } + hub.cache = make(chan *udp.Packet, hub.capacity) go hub.start() @@ -64,11 +74,21 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe // Close implements net.Listener. func (h *Hub) Close() error { + if h.connPacket != nil { + h.connPacket.Close() + return nil + } h.conn.Close() return nil } func (h *Hub) WriteTo(payload []byte, dest net.Destination) (int, error) { + if h.connPacket != nil { + return h.connPacket.WriteTo(payload, &net.UDPAddr{ + IP: dest.Address.IP(), + Port: int(dest.Port), + }) + } return h.conn.WriteToUDP(payload, &net.UDPAddr{ IP: dest.Address.IP(), Port: int(dest.Port), @@ -83,47 +103,76 @@ func (h *Hub) start() { for { buffer := buf.New() - var noob int - var addr *net.UDPAddr - rawBytes := buffer.Extend(buf.Size) - - n, noob, _, addr, err := ReadUDPMsg(h.conn, rawBytes, oobBytes) - if err != nil { - newError("failed to read UDP msg").Base(err).WriteToLog() - buffer.Release() - break - } - buffer.Resize(0, int32(n)) - - if buffer.IsEmpty() { - buffer.Release() - continue - } - - payload := &udp.Packet{ - Payload: buffer, - Source: net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), - } - if h.recvOrigDest && noob > 0 { - payload.Target = RetrieveOriginalDest(oobBytes[:noob]) - if payload.Target.IsValid() { - newError("UDP original destination: ", payload.Target).AtDebug().WriteToLog() - } else { - newError("failed to read UDP original destination").WriteToLog() + if h.conn != nil { + var noob int + var addr *net.UDPAddr + rawBytes := buffer.Extend(buf.Size) + n, noob, _, addr, err := ReadUDPMsg(h.conn, rawBytes, oobBytes) + if err != nil { + newError("failed to read UDP msg").Base(err).WriteToLog() + buffer.Release() + break } - } + buffer.Resize(0, int32(n)) - select { - case c <- payload: - default: - buffer.Release() - payload.Payload = nil + if buffer.IsEmpty() { + buffer.Release() + continue + } + + payload := &udp.Packet{ + Payload: buffer, + Source: net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), + } + if h.recvOrigDest && noob > 0 { + payload.Target = RetrieveOriginalDest(oobBytes[:noob]) + if payload.Target.IsValid() { + newError("UDP original destination: ", payload.Target).AtDebug().WriteToLog() + } else { + newError("failed to read UDP original destination").WriteToLog() + } + } + + select { + case c <- payload: + default: + buffer.Release() + payload.Payload = nil + } + } else { + rawBytes := buffer.Extend(buf.Size) + n, addr, err := h.connPacket.ReadFrom(rawBytes) + if err != nil { + newError("failed to read UDP msg").Base(err).WriteToLog() + buffer.Release() + break + } + buffer.Resize(0, int32(n)) + + if buffer.IsEmpty() { + buffer.Release() + continue + } + + payload := &udp.Packet{ + Payload: buffer, + Source: net.DestinationFromAddr(addr), + } + select { + case c <- payload: + default: + buffer.Release() + payload.Payload = nil + } } } } // Addr implements net.Listener. func (h *Hub) Addr() net.Addr { + if h.conn == nil { + return h.connPacket.LocalAddr() + } return h.conn.LocalAddr() }