瀏覽代碼

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
Xiaokang Wang (Shelikhoo) 1 年之前
父節點
當前提交
7db39fb566
共有 31 個文件被更改,包括 2216 次插入125 次删除
  1. 2 1
      go.mod
  2. 1 0
      go.sum
  3. 4 0
      main/distro/all/all.go
  4. 138 37
      proxy/freedom/config.pb.go
  5. 10 0
      proxy/freedom/config.proto
  6. 13 2
      proxy/freedom/freedom.go
  7. 236 0
      transport/internet/dtls/config.pb.go
  8. 25 0
      transport/internet/dtls/config.proto
  9. 57 0
      transport/internet/dtls/dialer.go
  10. 16 0
      transport/internet/dtls/dtls.go
  11. 9 0
      transport/internet/dtls/errors.generated.go
  12. 212 0
      transport/internet/dtls/listener.go
  13. 6 1
      transport/internet/kcp/dialer.go
  14. 17 2
      transport/internet/kcp/kcp_test.go
  15. 9 0
      transport/internet/request/assembler/packetconn/errors.generated.go
  16. 342 0
      transport/internet/request/assembler/packetconn/packetConn.pb.go
  17. 35 0
      transport/internet/request/assembler/packetconn/packetConn.proto
  18. 48 0
      transport/internet/request/assembler/packetconn/packetbundle.go
  19. 3 0
      transport/internet/request/assembler/packetconn/packetconn.go
  20. 429 0
      transport/internet/request/assembler/packetconn/req2packet.go
  21. 78 0
      transport/internet/request/assembler/packetconn/udpassembler.go
  22. 81 0
      transport/internet/request/assembler/packetconn/udpassemblerClient.go
  23. 166 0
      transport/internet/request/assembler/packetconn/udpassemblerServer.go
  24. 11 0
      transport/internet/request/assembly/hub.go
  25. 10 0
      transport/internet/request/roundtripper.go
  26. 54 34
      transport/internet/request/roundtripper/httprt/config.pb.go
  27. 2 0
      transport/internet/request/roundtripper/httprt/config.proto
  28. 57 8
      transport/internet/request/roundtripper/httprt/httprt.go
  29. 11 0
      transport/internet/tls/config.go
  30. 51 6
      transport/internet/transportcommon/httpDialer.go
  31. 83 34
      transport/internet/udp/hub.go

+ 2 - 1
go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/go-chi/chi/v5 v5.0.12
 	github.com/go-chi/chi/v5 v5.0.12
 	github.com/go-chi/render v1.0.3
 	github.com/go-chi/render v1.0.3
 	github.com/go-playground/validator/v10 v10.20.0
 	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/mock v1.6.0
 	github.com/golang/protobuf v1.5.4
 	github.com/golang/protobuf v1.5.4
 	github.com/google/go-cmp v0.6.0
 	github.com/google/go-cmp v0.6.0
@@ -19,6 +20,7 @@ require (
 	github.com/miekg/dns v1.1.59
 	github.com/miekg/dns v1.1.59
 	github.com/mustafaturan/bus v1.0.2
 	github.com/mustafaturan/bus v1.0.2
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pelletier/go-toml v1.9.5
+	github.com/pion/dtls/v2 v2.2.7
 	github.com/pion/transport/v2 v2.2.5
 	github.com/pion/transport/v2 v2.2.5
 	github.com/pires/go-proxyproto v0.7.0
 	github.com/pires/go-proxyproto v0.7.0
 	github.com/quic-go/quic-go v0.43.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/mustafaturan/monoton v1.0.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.10.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.10.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
-	github.com/pion/dtls/v2 v2.2.7 // indirect
 	github.com/pion/logging v0.2.2 // indirect
 	github.com/pion/logging v0.2.2 // indirect
 	github.com/pion/randutil v0.1.0 // indirect
 	github.com/pion/randutil v0.1.0 // indirect
 	github.com/pion/sctp v1.8.7 // indirect
 	github.com/pion/sctp v1.8.7 // indirect

+ 1 - 0
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/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.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 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-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/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=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=

+ 4 - 0
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/assembler/simple"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/request/roundtripper/httprt"
 	_ "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/request/stereotype/meek"
 
 
+	_ "github.com/v2fly/v2ray-core/v5/transport/internet/dtls"
+
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/httpupgrade"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/httpupgrade"
 
 
 	// Transport headers
 	// Transport headers

+ 138 - 37
proxy/freedom/config.pb.go

@@ -16,6 +16,55 @@ const (
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 	_ = 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
 type Config_DomainStrategy int32
 
 
 const (
 const (
@@ -52,11 +101,11 @@ func (x Config_DomainStrategy) String() string {
 }
 }
 
 
 func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
 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 {
 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 {
 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"`
 	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"`
 	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"`
 	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() {
 func (x *Config) Reset() {
@@ -188,10 +238,20 @@ func (x *Config) GetUserLevel() uint32 {
 	return 0
 	return 0
 }
 }
 
 
+func (x *Config) GetProtocolReplacement() ProtocolReplacement {
+	if x != nil {
+		return x.ProtocolReplacement
+	}
+	return ProtocolReplacement_IDENTITY
+}
+
 type SimplifiedConfig struct {
 type SimplifiedConfig struct {
 	state         protoimpl.MessageState
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	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() {
 func (x *SimplifiedConfig) Reset() {
@@ -226,6 +286,20 @@ func (*SimplifiedConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_freedom_config_proto_rawDescGZIP(), []int{2}
 	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 protoreflect.FileDescriptor
 
 
 var file_proxy_freedom_config_proto_rawDesc = []byte{
 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,
 	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,
 	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,
 	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,
 	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,
 	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,
 	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,
 	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,
 	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,
 	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 (
 var (
@@ -287,24 +384,28 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
 	return file_proxy_freedom_config_proto_rawDescData
 	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_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{
 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() }
 func init() { file_proxy_freedom_config_proto_init() }
@@ -313,7 +414,7 @@ func file_proxy_freedom_config_proto_init() {
 		return
 		return
 	}
 	}
 	if !protoimpl.UnsafeEnabled {
 	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 {
 			switch v := v.(*DestinationOverride); i {
 			case 0:
 			case 0:
 				return &v.state
 				return &v.state
@@ -325,7 +426,7 @@ func file_proxy_freedom_config_proto_init() {
 				return nil
 				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 {
 			switch v := v.(*Config); i {
 			case 0:
 			case 0:
 				return &v.state
 				return &v.state
@@ -337,7 +438,7 @@ func file_proxy_freedom_config_proto_init() {
 				return nil
 				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 {
 			switch v := v.(*SimplifiedConfig); i {
 			case 0:
 			case 0:
 				return &v.state
 				return &v.state
@@ -355,7 +456,7 @@ func file_proxy_freedom_config_proto_init() {
 		File: protoimpl.DescBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_proxy_freedom_config_proto_rawDesc,
 			RawDescriptor: file_proxy_freedom_config_proto_rawDesc,
-			NumEnums:      1,
+			NumEnums:      2,
 			NumMessages:   3,
 			NumMessages:   3,
 			NumExtensions: 0,
 			NumExtensions: 0,
 			NumServices:   0,
 			NumServices:   0,

+ 10 - 0
proxy/freedom/config.proto

@@ -13,6 +13,12 @@ message DestinationOverride {
   v2ray.core.common.protocol.ServerEndpoint server = 1;
   v2ray.core.common.protocol.ServerEndpoint server = 1;
 }
 }
 
 
+enum ProtocolReplacement {
+  IDENTITY = 0;
+  FORCE_TCP = 1;
+  FORCE_UDP = 2;
+}
+
 message Config {
 message Config {
   enum DomainStrategy {
   enum DomainStrategy {
     AS_IS = 0;
     AS_IS = 0;
@@ -24,9 +30,13 @@ message Config {
   uint32 timeout = 2 [deprecated = true];
   uint32 timeout = 2 [deprecated = true];
   DestinationOverride destination_override = 3;
   DestinationOverride destination_override = 3;
   uint32 user_level = 4;
   uint32 user_level = 4;
+  ProtocolReplacement protocol_replacement = 5;
 }
 }
 
 
 message SimplifiedConfig {
 message SimplifiedConfig {
   option (v2ray.core.common.protoext.message_opt).type = "outbound";
   option (v2ray.core.common.protoext.message_opt).type = "outbound";
   option (v2ray.core.common.protoext.message_opt).short_name = "freedom";
   option (v2ray.core.common.protoext.message_opt).short_name = "freedom";
+
+  DestinationOverride destination_override = 3;
+  ProtocolReplacement protocol_replacement = 5;
 }
 }

+ 13 - 2
proxy/freedom/freedom.go

@@ -35,7 +35,10 @@ func init() {
 	common.Must(common.RegisterConfig((*SimplifiedConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 	common.Must(common.RegisterConfig((*SimplifiedConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		simplifiedServer := config.(*SimplifiedConfig)
 		simplifiedServer := config.(*SimplifiedConfig)
 		_ = simplifiedServer
 		_ = simplifiedServer
-		fullConfig := &Config{}
+		fullConfig := &Config{
+			DestinationOverride: simplifiedServer.DestinationOverride,
+			ProtocolReplacement: simplifiedServer.ProtocolReplacement,
+		}
 		return common.CreateObject(ctx, fullConfig)
 		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)
 			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() {
 	if h.config.useIP() {
 		outbound.Resolver = func(ctx context.Context, domain string) net.Address {
 		outbound.Resolver = func(ctx context.Context, domain string) net.Address {
 			return h.resolveIP(ctx, domain, dialer.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)
 		defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
 
 
 		var reader buf.Reader
 		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)
 			reader = buf.NewReader(conn)
 		} else {
 		} else {
 			reader = buf.NewPacketReader(conn)
 			reader = buf.NewPacketReader(conn)

+ 236 - 0
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
+}

+ 25 - 0
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;
+}

+ 57 - 0
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))
+}

+ 16 - 0
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)
+	}))
+}

+ 9 - 0
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{})
+}

+ 212 - 0
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))
+}

+ 6 - 1
transport/internet/kcp/dialer.go

@@ -8,6 +8,8 @@ import (
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/buf"
 	"github.com/v2fly/v2ray-core/v5/common/buf"
 	"github.com/v2fly/v2ray-core/v5/common/dice"
 	"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/common/net"
 	"github.com/v2fly/v2ray-core/v5/transport/internet"
 	"github.com/v2fly/v2ray-core/v5/transport/internet"
 	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
 	"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
 	dest.Network = net.Network_UDP
 	newError("dialing mKCP to ", dest).WriteToLog()
 	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 {
 	if err != nil {
 		return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err)
 		return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err)
 	}
 	}

+ 17 - 2
transport/internet/kcp/kcp_test.go

@@ -7,6 +7,11 @@ import (
 	"testing"
 	"testing"
 	"time"
 	"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"
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
@@ -18,7 +23,17 @@ import (
 )
 )
 
 
 func TestDialAndListen(t *testing.T) {
 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",
 		ProtocolName:     "mkcp",
 		ProtocolSettings: &Config{},
 		ProtocolSettings: &Config{},
 	}, func(conn internet.Connection) {
 	}, func(conn internet.Connection) {
@@ -45,7 +60,7 @@ func TestDialAndListen(t *testing.T) {
 	var errg errgroup.Group
 	var errg errgroup.Group
 	for i := 0; i < 10; i++ {
 	for i := 0; i < 10; i++ {
 		errg.Go(func() error {
 		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",
 				ProtocolName:     "mkcp",
 				ProtocolSettings: &Config{},
 				ProtocolSettings: &Config{},
 			})
 			})

+ 9 - 0
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{})
+}

+ 342 - 0
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
+}

+ 35 - 0
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;
+}

+ 48 - 0
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)
+}

+ 3 - 0
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

+ 429 - 0
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
+		}
+	}
+}

+ 78 - 0
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)
+	}))
+}

+ 81 - 0
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)
+}

+ 166 - 0
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()
+		}
+	})
+}

+ 11 - 0
transport/internet/request/assembly/hub.go

@@ -36,6 +36,11 @@ func (s server) Close() error {
 	if err := s.tripper.Close(); err != nil {
 	if err := s.tripper.Close(); err != nil {
 		return newError("failed to close tripper").Base(err)
 		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
 	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)
 		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
 	return serverAssembly, nil
 }
 }
 
 

+ 10 - 0
transport/internet/request/roundtripper.go

@@ -2,6 +2,7 @@ package request
 
 
 import (
 import (
 	"context"
 	"context"
+	"io"
 
 
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common"
 )
 )
@@ -36,3 +37,12 @@ type Request struct {
 type Response struct {
 type Response struct {
 	Data []byte
 	Data []byte
 }
 }
+
+type OptionSupportsStreamingResponse interface {
+	RoundTripperOption
+	GetResponseWriter() io.Writer
+}
+
+type OptionSupportsStreamingResponseExtensionFlusher interface {
+	Flush()
+}

+ 54 - 34
transport/internet/request/roundtripper/httprt/config.pb.go

@@ -20,7 +20,9 @@ type ClientConfig struct {
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	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() {
 func (x *ClientConfig) Reset() {
@@ -62,6 +64,20 @@ func (x *ClientConfig) GetHttp() *HTTPConfig {
 	return nil
 	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 {
 type ServerConfig struct {
 	state         protoimpl.MessageState
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	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,
 	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,
 	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,
 	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,
 	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,
 	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,
 	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,
 	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, 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,
 	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,
 	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,
 	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,
 	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 (
 var (

+ 2 - 0
transport/internet/request/roundtripper/httprt/config.proto

@@ -13,6 +13,8 @@ message ClientConfig {
   option (v2ray.core.common.protoext.message_opt).short_name = "httprt";
   option (v2ray.core.common.protoext.message_opt).short_name = "httprt";
 
 
   HTTPConfig http = 1;
   HTTPConfig http = 1;
+  bool allow_http = 2;
+  int32 h2_pool_size = 3;
 }
 }
 
 
 message ServerConfig {
 message ServerConfig {

+ 57 - 8
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) {
 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 {
 	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)
 			return h.assembly.AutoImplDialer().Dial(ctx)
-		}, unimplementedBackDrop{})
+		}, backDrop, int(h.config.H2PoolSize))
 	}
 	}
 
 
 	connectionTagStr := base64.RawURLEncoding.EncodeToString(req.ConnectionTag)
 	connectionTagStr := base64.RawURLEncoding.EncodeToString(req.ConnectionTag)
 
 
 	httpRequest, err := http.NewRequest("POST", h.config.Http.UrlPrefix+h.config.Http.Path, bytes.NewReader(req.Data))
 	httpRequest, err := http.NewRequest("POST", h.config.Http.UrlPrefix+h.config.Http.Path, bytes.NewReader(req.Data))
 	if err != nil {
 	if err != nil {
-		return
+		return resp, err
 	}
 	}
 
 
 	httpRequest.Header.Set("X-Session-ID", connectionTagStr)
 	httpRequest.Header.Set("X-Session-ID", connectionTagStr)
 
 
 	httpResp, err := h.httpRTT.RoundTrip(httpRequest)
 	httpResp, err := h.httpRTT.RoundTrip(httpRequest)
 	if err != nil {
 	if err != nil {
-		return
+		return resp, err
 	}
 	}
 	defer httpResp.Body.Close()
 	defer httpResp.Body.Close()
-	result, err := io.ReadAll(httpResp.Body)
+	if streamingWriter == nil {
+		result, err := io.ReadAll(httpResp.Body)
+		if err != nil {
+			return request.Response{}, err
+		}
+		return request.Response{Data: result}, err
+	}
+	_, err = io.Copy(streamingWriter, httpResp.Body)
 	if err != nil {
 	if err != nil {
-		return
+		return request.Response{}, newError("unable to copy response").Base(err)
 	}
 	}
-	return request.Response{Data: result}, err
+	return request.Response{}, nil
 }
 }
 
 
 func newHTTPRoundTripperServer(ctx context.Context, config *ServerConfig) request.RoundTripperServer {
 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)
 	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) {
 func (h *httpTripperServer) onRequest(resp http.ResponseWriter, req *http.Request) {
 	tail := req.Header.Get("X-Session-ID")
 	tail := req.Header.Get("X-Session-ID")
 	data := []byte(tail)
 	data := []byte(tail)
@@ -103,10 +146,16 @@ func (h *httpTripperServer) onRequest(resp http.ResponseWriter, req *http.Reques
 	if err != nil {
 	if err != nil {
 		newError("unable to read body").Base(err).AtInfo().WriteToLog()
 		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 {
 	if err != nil {
 		newError("unable to process roundtrip").Base(err).AtInfo().WriteToLog()
 		newError("unable to process roundtrip").Base(err).AtInfo().WriteToLog()
 	}
 	}
+	if streamingRespOption.used {
+		return
+	}
 	_, err = io.Copy(resp, bytes.NewReader(recvResp.Data))
 	_, err = io.Copy(resp, bytes.NewReader(recvResp.Data))
 	if err != nil {
 	if err != nil {
 		newError("unable to send response").Base(err).AtInfo().WriteToLog()
 		newError("unable to send response").Base(err).AtInfo().WriteToLog()

+ 11 - 0
transport/internet/tls/config.go

@@ -5,6 +5,7 @@ import (
 	"crypto/tls"
 	"crypto/tls"
 	"crypto/x509"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/base64"
+	"os"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
@@ -194,6 +195,16 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert
 	return nil
 	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.
 // GetTLSConfig converts this Config into tls.Config.
 func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
 func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
 	root, err := c.getCertPool()
 	root, err := c.getCertPool()

+ 51 - 6
transport/internet/transportcommon/httpDialer.go

@@ -17,11 +17,18 @@ import (
 
 
 type DialerFunc func(ctx context.Context, addr string) (net.Conn, error)
 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,
 func NewALPNAwareHTTPRoundTripper(ctx context.Context, dialer DialerFunc,
 	backdropTransport http.RoundTripper,
 	backdropTransport http.RoundTripper,
 ) 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{
 	rtImpl := &alpnAwareHTTPRoundTripperImpl{
 		connectWithH1:     map[string]bool{},
 		connectWithH1:     map[string]bool{},
 		backdropTransport: backdropTransport,
 		backdropTransport: backdropTransport,
@@ -46,6 +53,8 @@ type alpnAwareHTTPRoundTripperImpl struct {
 
 
 	ctx    context.Context
 	ctx    context.Context
 	dialer DialerFunc
 	dialer DialerFunc
+
+	h2PoolSize int
 }
 }
 
 
 type pendingConnKey struct {
 type pendingConnKey struct {
@@ -175,10 +184,20 @@ func (r *alpnAwareHTTPRoundTripperImpl) dialTLS(ctx context.Context, addr string
 }
 }
 
 
 func (r *alpnAwareHTTPRoundTripperImpl) init() {
 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{
 	r.httpsH1Transport = &http.Transport{
 		DialTLSContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
 		DialTLSContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
@@ -220,3 +239,29 @@ func (c *unclaimedConnection) tick() {
 		c.Conn = nil
 		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)
+}

+ 83 - 34
transport/internet/udp/hub.go

@@ -3,6 +3,9 @@ package udp
 import (
 import (
 	"context"
 	"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/buf"
 	"github.com/v2fly/v2ray-core/v5/common/net"
 	"github.com/v2fly/v2ray-core/v5/common/net"
 	"github.com/v2fly/v2ray-core/v5/common/protocol/udp"
 	"github.com/v2fly/v2ray-core/v5/common/protocol/udp"
@@ -25,6 +28,7 @@ func HubReceiveOriginalDestination(r bool) HubOption {
 
 
 type Hub struct {
 type Hub struct {
 	conn         *net.UDPConn
 	conn         *net.UDPConn
+	connPacket   net.PacketConn
 	cache        chan *udp.Packet
 	cache        chan *udp.Packet
 	capacity     int
 	capacity     int
 	recvOrigDest bool
 	recvOrigDest bool
@@ -46,8 +50,9 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe
 	if sockopt != nil && sockopt.ReceiveOriginalDestAddress {
 	if sockopt != nil && sockopt.ReceiveOriginalDestAddress {
 		hub.recvOrigDest = true
 		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(),
 		IP:   address.IP(),
 		Port: int(port),
 		Port: int(port),
 	}, sockopt)
 	}, sockopt)
@@ -55,7 +60,12 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe
 		return nil, err
 		return nil, err
 	}
 	}
 	newError("listening UDP on ", address, ":", port).WriteToLog()
 	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)
 	hub.cache = make(chan *udp.Packet, hub.capacity)
 
 
 	go hub.start()
 	go hub.start()
@@ -64,11 +74,21 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe
 
 
 // Close implements net.Listener.
 // Close implements net.Listener.
 func (h *Hub) Close() error {
 func (h *Hub) Close() error {
+	if h.connPacket != nil {
+		h.connPacket.Close()
+		return nil
+	}
 	h.conn.Close()
 	h.conn.Close()
 	return nil
 	return nil
 }
 }
 
 
 func (h *Hub) WriteTo(payload []byte, dest net.Destination) (int, error) {
 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{
 	return h.conn.WriteToUDP(payload, &net.UDPAddr{
 		IP:   dest.Address.IP(),
 		IP:   dest.Address.IP(),
 		Port: int(dest.Port),
 		Port: int(dest.Port),
@@ -83,47 +103,76 @@ func (h *Hub) start() {
 
 
 	for {
 	for {
 		buffer := buf.New()
 		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 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))
 
 
-		if buffer.IsEmpty() {
-			buffer.Release()
-			continue
-		}
+			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()
+			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))
 
 
-		select {
-		case c <- payload:
-		default:
-			buffer.Release()
-			payload.Payload = nil
+			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.
 // Addr implements net.Listener.
 func (h *Hub) Addr() net.Addr {
 func (h *Hub) Addr() net.Addr {
+	if h.conn == nil {
+		return h.connPacket.LocalAddr()
+	}
 	return h.conn.LocalAddr()
 	return h.conn.LocalAddr()
 }
 }