浏览代码

Add Add Hysteria2 Protocol

Jimmy Huang 1 年之前
父节点
当前提交
2078480c4e

+ 1 - 1
.github/workflows/linter.yml

@@ -22,7 +22,7 @@ jobs:
       - name: Set up Go 1.x
         uses: actions/setup-go@v4
         with:
-          go-version: ^1.19
+          go-version: ^1.21
 
       - name: Checkout codebase
         uses: actions/checkout@v3

+ 2 - 2
.github/workflows/test.yml

@@ -5,7 +5,7 @@ on:
     branches:
       - master
       - v*
-      - dev-*
+      - dev*
     paths:
       - "**/*.go"
       - "go.mod"
@@ -30,7 +30,7 @@ jobs:
       - name: Set up Go 1.x
         uses: actions/setup-go@v4
         with:
-          go-version: ^1.19
+          go-version: ^1.21
 
       - name: Checkout codebase
         uses: actions/checkout@v3

+ 2 - 0
.gitignore

@@ -26,3 +26,5 @@ vprotogen
 errorgen
 !common/errors/errorgen/
 *.dat
+*~
+[._]*.un~

+ 1 - 1
README.md

@@ -32,7 +32,7 @@ This repo relies on the following third-party projects:
 
 - In production:
   - [gorilla/websocket](https://github.com/gorilla/websocket)
-  - [lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go)
+  - [quic-go/quic-go](https://github.com/quic-go/quic-go)
   - [pires/go-proxyproto](https://github.com/pires/go-proxyproto)
   - [seiflotfy/cuckoofilter](https://github.com/seiflotfy/cuckoofilter)
   - [google/starlark-go](https://github.com/google/starlark-go)

+ 0 - 6
app/dns/config.pb.go

@@ -1,9 +1,3 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.31.0
-// 	protoc        v4.24.4
-// source: app/dns/config.proto
-
 package dns
 
 import (

+ 13 - 2
app/router/command/command_test.go

@@ -9,6 +9,7 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/test/bufconn"
 
 	"github.com/v2fly/v2ray-core/v5/app/router"
@@ -90,7 +91,13 @@ func TestServiceSubscribeRoutingStats(t *testing.T) {
 	// Client goroutine
 	go func() {
 		defer lis.Close()
-		conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
+		conn, err := grpc.DialContext(
+			context.Background(),
+			"bufnet",
+			grpc.WithContextDialer(bufDialer),
+			grpc.WithTransportCredentials(insecure.NewCredentials()),
+		)
+
 		if err != nil {
 			errCh <- err
 			return
@@ -268,7 +275,11 @@ func TestSerivceTestRoute(t *testing.T) {
 	// Client goroutine
 	go func() {
 		defer lis.Close()
-		conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
+		conn, err := grpc.DialContext(
+			context.Background(),
+			"bufnet", grpc.WithContextDialer(bufDialer),
+			grpc.WithTransportCredentials(insecure.NewCredentials()),
+		)
 		if err != nil {
 			errCh <- err
 		}

+ 115 - 120
app/router/config.pb.go

@@ -1,18 +1,12 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.27.1
-// 	protoc        v3.12.4
-// source: app/router/config.proto
-
 package router
 
 import (
-	any "github.com/golang/protobuf/ptypes/any"
 	routercommon "github.com/v2fly/v2ray-core/v5/app/router/routercommon"
 	net "github.com/v2fly/v2ray-core/v5/common/net"
 	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	anypb "google.golang.org/protobuf/types/known/anypb"
 	reflect "reflect"
 	sync "sync"
 )
@@ -86,6 +80,7 @@ type RoutingRule struct {
 	unknownFields protoimpl.UnknownFields
 
 	// Types that are assignable to TargetTag:
+	//
 	//	*RoutingRule_Tag
 	//	*RoutingRule_BalancingTag
 	TargetTag isRoutingRule_TargetTag `protobuf_oneof:"target_tag"`
@@ -94,7 +89,7 @@ type RoutingRule struct {
 	// List of CIDRs for target IP address matching.
 	// Deprecated. Use geoip below.
 	//
-	// Deprecated: Do not use.
+	// Deprecated: Marked as deprecated in app/router/config.proto.
 	Cidr []*routercommon.CIDR `protobuf:"bytes,3,rep,name=cidr,proto3" json:"cidr,omitempty"`
 	// List of GeoIPs for target IP address matching. If this entry exists, the
 	// cidr above will have no effect. GeoIP fields with the same country code are
@@ -104,19 +99,19 @@ type RoutingRule struct {
 	// A range of port [from, to]. If the destination port is in this range, this
 	// rule takes effect. Deprecated. Use port_list.
 	//
-	// Deprecated: Do not use.
+	// Deprecated: Marked as deprecated in app/router/config.proto.
 	PortRange *net.PortRange `protobuf:"bytes,4,opt,name=port_range,json=portRange,proto3" json:"port_range,omitempty"`
 	// List of ports.
 	PortList *net.PortList `protobuf:"bytes,14,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
 	// List of networks. Deprecated. Use networks.
 	//
-	// Deprecated: Do not use.
+	// Deprecated: Marked as deprecated in app/router/config.proto.
 	NetworkList *net.NetworkList `protobuf:"bytes,5,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"`
 	// List of networks for matching.
 	Networks []net.Network `protobuf:"varint,13,rep,packed,name=networks,proto3,enum=v2ray.core.common.net.Network" json:"networks,omitempty"`
 	// List of CIDRs for source IP address matching.
 	//
-	// Deprecated: Do not use.
+	// Deprecated: Marked as deprecated in app/router/config.proto.
 	SourceCidr []*routercommon.CIDR `protobuf:"bytes,6,rep,name=source_cidr,json=sourceCidr,proto3" json:"source_cidr,omitempty"`
 	// List of GeoIPs for source IP address matching. If this entry exists, the
 	// source_cidr above will have no effect.
@@ -192,7 +187,7 @@ func (x *RoutingRule) GetDomain() []*routercommon.Domain {
 	return nil
 }
 
-// Deprecated: Do not use.
+// Deprecated: Marked as deprecated in app/router/config.proto.
 func (x *RoutingRule) GetCidr() []*routercommon.CIDR {
 	if x != nil {
 		return x.Cidr
@@ -207,7 +202,7 @@ func (x *RoutingRule) GetGeoip() []*routercommon.GeoIP {
 	return nil
 }
 
-// Deprecated: Do not use.
+// Deprecated: Marked as deprecated in app/router/config.proto.
 func (x *RoutingRule) GetPortRange() *net.PortRange {
 	if x != nil {
 		return x.PortRange
@@ -222,7 +217,7 @@ func (x *RoutingRule) GetPortList() *net.PortList {
 	return nil
 }
 
-// Deprecated: Do not use.
+// Deprecated: Marked as deprecated in app/router/config.proto.
 func (x *RoutingRule) GetNetworkList() *net.NetworkList {
 	if x != nil {
 		return x.NetworkList
@@ -237,7 +232,7 @@ func (x *RoutingRule) GetNetworks() []net.Network {
 	return nil
 }
 
-// Deprecated: Do not use.
+// Deprecated: Marked as deprecated in app/router/config.proto.
 func (x *RoutingRule) GetSourceCidr() []*routercommon.CIDR {
 	if x != nil {
 		return x.SourceCidr
@@ -324,11 +319,11 @@ type BalancingRule struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Tag              string   `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
-	OutboundSelector []string `protobuf:"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3" json:"outbound_selector,omitempty"`
-	Strategy         string   `protobuf:"bytes,3,opt,name=strategy,proto3" json:"strategy,omitempty"`
-	StrategySettings *any.Any `protobuf:"bytes,4,opt,name=strategy_settings,json=strategySettings,proto3" json:"strategy_settings,omitempty"`
-	FallbackTag      string   `protobuf:"bytes,5,opt,name=fallback_tag,json=fallbackTag,proto3" json:"fallback_tag,omitempty"`
+	Tag              string     `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
+	OutboundSelector []string   `protobuf:"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3" json:"outbound_selector,omitempty"`
+	Strategy         string     `protobuf:"bytes,3,opt,name=strategy,proto3" json:"strategy,omitempty"`
+	StrategySettings *anypb.Any `protobuf:"bytes,4,opt,name=strategy_settings,json=strategySettings,proto3" json:"strategy_settings,omitempty"`
+	FallbackTag      string     `protobuf:"bytes,5,opt,name=fallback_tag,json=fallbackTag,proto3" json:"fallback_tag,omitempty"`
 }
 
 func (x *BalancingRule) Reset() {
@@ -384,7 +379,7 @@ func (x *BalancingRule) GetStrategy() string {
 	return ""
 }
 
-func (x *BalancingRule) GetStrategySettings() *any.Any {
+func (x *BalancingRule) GetStrategySettings() *anypb.Any {
 	if x != nil {
 		return x.StrategySettings
 	}
@@ -724,6 +719,7 @@ type SimplifiedRoutingRule struct {
 	unknownFields protoimpl.UnknownFields
 
 	// Types that are assignable to TargetTag:
+	//
 	//	*SimplifiedRoutingRule_Tag
 	//	*SimplifiedRoutingRule_BalancingTag
 	TargetTag isSimplifiedRoutingRule_TargetTag `protobuf_oneof:"target_tag"`
@@ -1068,110 +1064,109 @@ var file_app_router_config_proto_rawDesc = []byte{
 	0x67, 0x65, 0x78, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20,
 	0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
 	0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x22, 0x74, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x61, 0x6e, 0x64,
+	0x22, 0x70, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x61, 0x6e, 0x64,
 	0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x62, 0x73, 0x65,
 	0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
 	0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x61, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x61,
 	0x6c, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x09, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x3a, 0x1a, 0x82, 0xb5, 0x18, 0x0a,
-	0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x82, 0xb5, 0x18, 0x08, 0x12, 0x06,
-	0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x22, 0x5b, 0x0a, 0x17, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
-	0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x61,
-	0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65,
-	0x72, 0x54, 0x61, 0x67, 0x3a, 0x1d, 0x82, 0xb5, 0x18, 0x0a, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61,
-	0x6e, 0x63, 0x65, 0x72, 0x82, 0xb5, 0x18, 0x0b, 0x12, 0x09, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x70,
-	0x69, 0x6e, 0x67, 0x22, 0x88, 0x02, 0x0a, 0x17, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x3b, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25,
-	0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
-	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57,
-	0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09,
-	0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
-	0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78,
-	0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78,
-	0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c,
-	0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
-	0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c,
-	0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x61, 0x67, 0x3a,
-	0x1d, 0x82, 0xb5, 0x18, 0x0a, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x82,
-	0xb5, 0x18, 0x0b, 0x12, 0x09, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xdd,
-	0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d,
-	0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0e, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
-	0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c,
-	0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
-	0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e,
-	0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c,
-	0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72,
-	0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61,
-	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
-	0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52,
-	0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0xab,
-	0x05, 0x0a, 0x15, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x6f, 0x75,
-	0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d,
-	0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20,
-	0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67,
-	0x54, 0x61, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
-	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74,
-	0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
-	0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3f, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70,
-	0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x72,
-	0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x6f, 0x49,
-	0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74,
-	0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x72,
-	0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
-	0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
-	0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e,
-	0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x74,
-	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x4c, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
-	0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x76, 0x32,
+	0x09, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x3a, 0x16, 0x82, 0xb5, 0x18, 0x12,
+	0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12, 0x06, 0x72, 0x61, 0x6e, 0x64,
+	0x6f, 0x6d, 0x22, 0x57, 0x0a, 0x17, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65,
+	0x61, 0x73, 0x74, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a,
+	0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x61, 0x67,
+	0x3a, 0x19, 0x82, 0xb5, 0x18, 0x15, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72,
+	0x12, 0x09, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x84, 0x02, 0x0a, 0x17,
+	0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61,
+	0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
+	0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53,
+	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63,
+	0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65,
+	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e,
+	0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16,
+	0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
+	0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61,
+	0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72,
+	0x61, 0x6e, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
+	0x5f, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x62, 0x73, 0x65,
+	0x72, 0x76, 0x65, 0x72, 0x54, 0x61, 0x67, 0x3a, 0x19, 0x82, 0xb5, 0x18, 0x15, 0x0a, 0x08, 0x62,
+	0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12, 0x09, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x6c, 0x6f,
+	0x61, 0x64, 0x22, 0xdd, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4e, 0x0a,
+	0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
+	0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a,
+	0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32,
 	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
-	0x74, 0x65, 0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
-	0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x65,
-	0x6f, 0x69, 0x70, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f,
-	0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a,
-	0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b,
-	0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a,
-	0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52,
-	0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74,
-	0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61,
-	0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x6d,
-	0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
-	0x12, 0x4c, 0x0a, 0x0a, 0x67, 0x65, 0x6f, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0xa1,
-	0x93, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x72,
-	0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x6f, 0x53,
-	0x69, 0x74, 0x65, 0x52, 0x09, 0x67, 0x65, 0x6f, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x0c,
-	0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x8c, 0x02, 0x0a,
-	0x10, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61,
-	0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72,
+	0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52,
+	0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69,
+	0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e,
+	0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72,
+	0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52,
+	0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75,
+	0x6c, 0x65, 0x22, 0xab, 0x05, 0x0a, 0x15, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65,
+	0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03,
+	0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67,
+	0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61,
+	0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e,
+	0x63, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
+	0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e,
+	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x6d,
+	0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3f, 0x0a, 0x05, 0x67,
+	0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72,
 	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
-	0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
-	0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
-	0x79, 0x12, 0x40, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69,
-	0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72,
-	0x75, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67,
-	0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32,
-	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
-	0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c,
-	0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65,
-	0x3a, 0x19, 0x82, 0xb5, 0x18, 0x09, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x82,
-	0xb5, 0x18, 0x08, 0x12, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2a, 0x47, 0x0a, 0x0e, 0x44,
+	0x65, 0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x1b, 0x0a, 0x09,
+	0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x08, 0x6e, 0x65, 0x74,
+	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32,
+	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52,
+	0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x4c, 0x0a, 0x0c, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
+	0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f,
+	0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73,
+	0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18,
+	0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c,
+	0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18,
+	0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61,
+	0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a,
+	0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a,
+	0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18,
+	0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74,
+	0x63, 0x68, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x0a, 0x67, 0x65, 0x6f, 0x5f, 0x64, 0x6f, 0x6d, 0x61,
+	0x69, 0x6e, 0x18, 0xa1, 0x93, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72,
+	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
+	0x65, 0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x09, 0x67, 0x65, 0x6f, 0x44, 0x6f, 0x6d, 0x61,
+	0x69, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67,
+	0x22, 0x88, 0x02, 0x0a, 0x10, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f,
+	0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25,
+	0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
+	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
+	0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
+	0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x40, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70,
+	0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c,
+	0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e,
+	0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
+	0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e,
+	0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67,
+	0x52, 0x75, 0x6c, 0x65, 0x3a, 0x15, 0x82, 0xb5, 0x18, 0x11, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76,
+	0x69, 0x63, 0x65, 0x12, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2a, 0x47, 0x0a, 0x0e, 0x44,
 	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a,
 	0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70,
 	0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74,
@@ -1218,7 +1213,7 @@ var file_app_router_config_proto_goTypes = []interface{}{
 	(*net.NetworkList)(nil),         // 15: v2ray.core.common.net.NetworkList
 	(net.Network)(0),                // 16: v2ray.core.common.net.Network
 	(*routercommon.GeoSite)(nil),    // 17: v2ray.core.app.router.routercommon.GeoSite
-	(*any.Any)(nil),                 // 18: google.protobuf.Any
+	(*anypb.Any)(nil),               // 18: google.protobuf.Any
 }
 var file_app_router_config_proto_depIdxs = []int32{
 	10, // 0: v2ray.core.app.router.RoutingRule.domain:type_name -> v2ray.core.app.router.routercommon.Domain

+ 1 - 1
config.pb.go

@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.31.0
-// 	protoc        v3.17.3
+// 	protoc        v4.24.4
 // source: config.proto
 
 package core

+ 8 - 2
go.mod

@@ -6,6 +6,8 @@ toolchain go1.21.4
 
 require (
 	github.com/adrg/xdg v0.5.0
+	github.com/apernet/hysteria/core/v2 v2.4.5
+	github.com/apernet/quic-go v0.45.2-0.20240702221538-ed74cfbe8b6e
 	github.com/go-chi/chi/v5 v5.1.0
 	github.com/go-chi/render v1.0.3
 	github.com/go-playground/validator/v10 v10.22.0
@@ -61,21 +63,23 @@ require (
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
 	github.com/google/btree v1.1.2 // indirect
-	github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
+	github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07 // indirect
 	github.com/klauspost/compress v1.17.4 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/klauspost/reedsolomon v1.11.7 // indirect
 	github.com/leodido/go-urn v1.4.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.17.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pion/logging v0.2.2 // indirect
 	github.com/pion/randutil v0.1.0 // indirect
 	github.com/pion/sctp v1.8.7 // indirect
 	github.com/pion/transport/v3 v3.0.7 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/quic-go/qpack v0.4.0 // indirect
 	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect
+	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/xtaci/smux v1.5.24 // indirect
 	go.uber.org/mock v0.4.0 // indirect
 	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
@@ -87,3 +91,5 @@ require (
 )
 
 replace github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 => github.com/xiaokangwang/struc v0.0.0-20231031203518-0e381172f248
+
+replace github.com/apernet/hysteria/core/v2 v2.4.5 => github.com/JimmyHuang454/hysteria/core/v2 v2.0.0-20240724161647-b3347cf6334d

+ 24 - 10
go.sum

@@ -14,6 +14,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/FlowerWrong/water v0.0.0-20180301012659-01a4eaa1f6f2/go.mod h1:xrG5L7lq7T2DLnPr2frMnL906CNEoKRwLB+VYFhPq2w=
+github.com/JimmyHuang454/hysteria/core/v2 v2.0.0-20240724161647-b3347cf6334d h1:DN3vqWeuVa1anRkwCueIqPEUPnSyFCk4PR2LcyJKrZk=
+github.com/JimmyHuang454/hysteria/core/v2 v2.0.0-20240724161647-b3347cf6334d/go.mod h1:3OIt9vhWrxoHUMPm6WcpAg7jUEqfy6Q4IyFZA67Azcg=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
 github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
@@ -25,6 +27,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
 github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/apernet/quic-go v0.45.2-0.20240702221538-ed74cfbe8b6e h1:KBs8aBfKl5AKPKGpfn3bl0joDJXDq5fnH+AjFODiU+A=
+github.com/apernet/quic-go v0.45.2-0.20240702221538-ed74cfbe8b6e/go.mod h1:MjGWpXA31DZZWESdX3/PjIpSWIT1fOm8FNCqyXXFZFU=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@@ -77,8 +81,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
-github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -131,8 +135,8 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
+github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07 h1:57oOH2Mu5Nw16KnZAVLdlUjmPH/TSYCKTJgG0OVfX0Y=
+github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -187,8 +191,9 @@ github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9g
 github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -221,10 +226,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
 github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
-github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
-github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
-github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
+github.com/onsi/ginkgo/v2 v2.17.0 h1:kdnunFXpBjbzN56hcJHrXZ8M+LOkenKA7NnBzTNigTI=
+github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
+github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
+github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@@ -268,6 +273,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
 github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
 github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
 github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
@@ -276,6 +283,8 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@@ -304,6 +313,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -347,6 +358,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
 go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
+go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
 go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
 go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@@ -566,8 +579,9 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

+ 87 - 0
infra/conf/v4/hysteria2.go

@@ -0,0 +1,87 @@
+package v4
+
+import (
+	"github.com/golang/protobuf/proto"
+
+	"github.com/v2fly/v2ray-core/v5/common/protocol"
+	"github.com/v2fly/v2ray-core/v5/common/serial"
+	"github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon"
+	"github.com/v2fly/v2ray-core/v5/proxy/hysteria2"
+)
+
+// Hysteria2ServerTarget is configuration of a single hysteria2 server
+type Hysteria2ServerTarget struct {
+	Address *cfgcommon.Address `json:"address"`
+	Port    uint16             `json:"port"`
+	Email   string             `json:"email"`
+	Level   byte               `json:"level"`
+}
+
+// Hysteria2ClientConfig is configuration of hysteria2 servers
+type Hysteria2ClientConfig struct {
+	Servers []*Hysteria2ServerTarget `json:"servers"`
+}
+
+// Build implements Buildable
+func (c *Hysteria2ClientConfig) Build() (proto.Message, error) {
+	config := new(hysteria2.ClientConfig)
+
+	if len(c.Servers) == 0 {
+		return nil, newError("0 Hysteria2 server configured.")
+	}
+
+	serverSpecs := make([]*protocol.ServerEndpoint, len(c.Servers))
+	for idx, rec := range c.Servers {
+		if rec.Address == nil {
+			return nil, newError("Hysteria2 server address is not set.")
+		}
+		if rec.Port == 0 {
+			return nil, newError("Invalid Hysteria2 port.")
+		}
+		account := &hysteria2.Account{}
+		hysteria2 := &protocol.ServerEndpoint{
+			Address: rec.Address.Build(),
+			Port:    uint32(rec.Port),
+			User: []*protocol.User{
+				{
+					Level:   uint32(rec.Level),
+					Email:   rec.Email,
+					Account: serial.ToTypedMessage(account),
+				},
+			},
+		}
+
+		serverSpecs[idx] = hysteria2
+	}
+
+	config.Server = serverSpecs
+
+	return config, nil
+}
+
+// Hysteria2UserConfig is user configuration
+type Hysteria2UserConfig struct {
+	Level byte   `json:"level"`
+	Email string `json:"email"`
+}
+
+// Hysteria2ServerConfig is Inbound configuration
+type Hysteria2ServerConfig struct {
+	Clients []*Hysteria2UserConfig `json:"clients"`
+}
+
+// Build implements Buildable
+func (c *Hysteria2ServerConfig) Build() (proto.Message, error) {
+	config := new(hysteria2.ServerConfig)
+	config.Users = make([]*protocol.User, len(c.Clients))
+	for idx, rawUser := range c.Clients {
+		user := new(protocol.User)
+		account := &hysteria2.Account{}
+
+		user.Email = rawUser.Email
+		user.Level = uint32(rawUser.Level)
+		user.Account = serial.ToTypedMessage(account)
+		config.Users[idx] = user
+	}
+	return config, nil
+}

+ 37 - 0
infra/conf/v4/transport_internet.go

@@ -16,6 +16,7 @@ import (
 	"github.com/v2fly/v2ray-core/v5/transport/internet/domainsocket"
 	httpheader "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
 	"github.com/v2fly/v2ray-core/v5/transport/internet/http"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
 	"github.com/v2fly/v2ray-core/v5/transport/internet/kcp"
 	"github.com/v2fly/v2ray-core/v5/transport/internet/quic"
 	"github.com/v2fly/v2ray-core/v5/transport/internet/tcp"
@@ -137,6 +138,29 @@ func (c *TCPConfig) Build() (proto.Message, error) {
 	return config, nil
 }
 
+type Hy2ConfigCongestion struct {
+	Type     string `json:"type"`
+	UpMbps   uint64 `json:"up_mbps"`
+	DownMbps uint64 `json:"down_mbps"`
+}
+
+type Hy2Config struct {
+	Password        string              `json:"password"`
+	Congestion      Hy2ConfigCongestion `json:"congestion"`
+	UseUdpExtension bool                `json:"use_udp_extension"`
+}
+
+// Build implements Buildable.
+func (c *Hy2Config) Build() (proto.Message, error) {
+	return &hysteria2.Config{Password: c.Password,
+		Congestion: &hysteria2.Congestion{
+			Type:     c.Congestion.Type,
+			DownMbps: c.Congestion.DownMbps,
+			UpMbps:   c.Congestion.UpMbps,
+		},
+		UseUdpExtension: c.UseUdpExtension}, nil
+}
+
 type WebSocketConfig struct {
 	Path                 string            `json:"path"`
 	Headers              map[string]string `json:"headers"`
@@ -279,6 +303,8 @@ func (p TransportProtocol) Build() (string, error) {
 		return "quic", nil
 	case "gun", "grpc":
 		return "gun", nil
+	case "hy2", "hysteria2":
+		return "hysteria2", nil
 	default:
 		return "", newError("Config: unknown transport protocol: ", p)
 	}
@@ -296,6 +322,7 @@ type StreamConfig struct {
 	QUICSettings   *QUICConfig             `json:"quicSettings"`
 	GunSettings    *GunConfig              `json:"gunSettings"`
 	GRPCSettings   *GunConfig              `json:"grpcSettings"`
+	Hy2Settings    *Hy2Config              `json:"hy2Settings"`
 	SocketSettings *socketcfg.SocketConfig `json:"sockopt"`
 }
 
@@ -397,6 +424,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
 			Settings:     serial.ToTypedMessage(gs),
 		})
 	}
+	if c.Hy2Settings != nil {
+		hy2, err := c.Hy2Settings.Build()
+		if err != nil {
+			return nil, newError("Failed to build hy2 config.").Base(err)
+		}
+		config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
+			ProtocolName: "hysteria2",
+			Settings:     serial.ToTypedMessage(hy2),
+		})
+	}
 	if c.SocketSettings != nil {
 		ss, err := c.SocketSettings.Build()
 		if err != nil {

+ 2 - 0
infra/conf/v4/v2ray.go

@@ -35,6 +35,7 @@ var (
 		"vless":         func() interface{} { return new(VLessInboundConfig) },
 		"vmess":         func() interface{} { return new(VMessInboundConfig) },
 		"trojan":        func() interface{} { return new(TrojanServerConfig) },
+		"hysteria2":     func() interface{} { return new(Hysteria2ServerConfig) },
 	}, "protocol", "settings")
 
 	outboundConfigLoader = loader.NewJSONConfigLoader(loader.ConfigCreatorCache{
@@ -46,6 +47,7 @@ var (
 		"vless":       func() interface{} { return new(VLessOutboundConfig) },
 		"vmess":       func() interface{} { return new(VMessOutboundConfig) },
 		"trojan":      func() interface{} { return new(TrojanClientConfig) },
+		"hysteria2":   func() interface{} { return new(Hysteria2ClientConfig) },
 		"dns":         func() interface{} { return new(DNSOutboundConfig) },
 		"loopback":    func() interface{} { return new(LoopbackConfig) },
 	}, "protocol", "settings")

+ 12 - 1
infra/vprotogen/main.go

@@ -3,6 +3,7 @@ package main
 import (
 	"bufio"
 	"bytes"
+	"errors"
 	"fmt"
 	"go/build"
 	"io"
@@ -121,7 +122,17 @@ func getInstalledProtocVersion(protocPath string) (string, error) {
 	}
 	versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+(\.\d)*)`)
 	matched := versionRegexp.FindStringSubmatch(string(output))
-	return matched[1], nil
+	installedVersion := ""
+	if len(matched) == 0 {
+		return "", errors.New("Can not parse protoc version.")
+	}
+
+	if len(matched) == 2 {
+		installedVersion += "4." // in contrast to getProjectProtocVersion()
+	}
+	installedVersion += matched[1]
+	fmt.Println("Using protoc version: " + installedVersion)
+	return installedVersion, nil
 }
 
 func parseVersion(s string, width int) int64 {

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

@@ -53,6 +53,7 @@ import (
 	_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/inbound"
 	_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/outbound"
 
+	_ "github.com/v2fly/v2ray-core/v5/proxy/hysteria2"
 	_ "github.com/v2fly/v2ray-core/v5/proxy/shadowsocks2022"
 
 	// Transports
@@ -81,6 +82,8 @@ import (
 
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/httpupgrade"
 
+	_ "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
+
 	// Transport headers
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/noop"

+ 170 - 0
proxy/hysteria2/client.go

@@ -0,0 +1,170 @@
+package hysteria2
+
+import (
+	"context"
+
+	hyProtocol "github.com/apernet/hysteria/core/v2/international/protocol"
+
+	core "github.com/v2fly/v2ray-core/v5"
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/protocol"
+	"github.com/v2fly/v2ray-core/v5/common/retry"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/common/signal"
+	"github.com/v2fly/v2ray-core/v5/common/task"
+	"github.com/v2fly/v2ray-core/v5/features/policy"
+	"github.com/v2fly/v2ray-core/v5/proxy"
+	"github.com/v2fly/v2ray-core/v5/transport"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
+)
+
+// Client is an inbound handler
+type Client struct {
+	serverPicker  protocol.ServerPicker
+	policyManager policy.Manager
+}
+
+// NewClient create a new client.
+func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
+	serverList := protocol.NewServerList()
+	for _, rec := range config.Server {
+		s, err := protocol.NewServerSpecFromPB(rec)
+		if err != nil {
+			return nil, newError("failed to parse server spec").Base(err)
+		}
+		serverList.AddServer(s)
+	}
+	if serverList.Size() == 0 {
+		return nil, newError("0 server")
+	}
+
+	v := core.MustFromContext(ctx)
+	client := &Client{
+		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
+	}
+	return client, nil
+}
+
+// Process implements OutboundHandler.Process().
+func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
+	outbound := session.OutboundFromContext(ctx)
+	if outbound == nil || !outbound.Target.IsValid() {
+		return newError("target not specified")
+	}
+	destination := outbound.Target
+	network := destination.Network
+
+	var server *protocol.ServerSpec
+	var conn internet.Connection
+
+	err := retry.ExponentialBackoff(5, 100).On(func() error {
+		server = c.serverPicker.PickServer()
+		rawConn, err := dialer.Dial(ctx, server.Destination())
+		if err != nil {
+			return err
+		}
+
+		conn = rawConn
+		return nil
+	})
+	if err != nil {
+		return newError("failed to find an available destination").AtWarning().Base(err)
+	}
+	newError("tunneling request to ", destination, " via ", server.Destination().NetAddr()).WriteToLog(session.ExportIDToError(ctx))
+
+	iConn := conn
+	if statConn, ok := conn.(*internet.StatCouterConnection); ok {
+		iConn = statConn.Connection // will not count the UDP traffic.
+	}
+	hyConn, IsHy2Transport := iConn.(*hyTransport.HyConn)
+
+	if !IsHy2Transport && network == net.Network_UDP {
+		// hysteria2 need to use udp extension to proxy UDP.
+		return newError(hyTransport.CanNotUseUdpExtension)
+	}
+
+	defer conn.Close()
+
+	user := server.PickUser()
+	account, ok := user.Account.(*MemoryAccount)
+	if !ok {
+		return newError("user account is not valid")
+	}
+
+	sessionPolicy := c.policyManager.ForLevel(user.Level)
+	ctx, cancel := context.WithCancel(ctx)
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
+
+	postRequest := func() error {
+		defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
+
+		var bodyWriter buf.Writer
+		bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
+		connWriter := &ConnWriter{Writer: bufferWriter, Target: destination, Account: account}
+		bodyWriter = connWriter
+
+		if network == net.Network_UDP {
+			bodyWriter = &PacketWriter{Writer: connWriter, Target: destination, HyConn: hyConn}
+		} else {
+			// write some request payload to buffer
+			err = buf.CopyOnceTimeout(link.Reader, bodyWriter, proxy.FirstPayloadTimeout)
+			switch err {
+			case buf.ErrNotTimeoutReader, buf.ErrReadTimeout:
+				if err := connWriter.WriteTCPHeader(); err != nil {
+					return newError("failed to write request header").Base(err).AtWarning()
+				}
+			case nil:
+			default:
+				return newError("failed to write a request payload").Base(err).AtWarning()
+			}
+			// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
+			if err = bufferWriter.SetBuffered(false); err != nil {
+				return newError("failed to flush payload").Base(err).AtWarning()
+			}
+		}
+
+		if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil {
+			return newError("failed to transfer request payload").Base(err).AtInfo()
+		}
+
+		return nil
+	}
+
+	getResponse := func() error {
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
+
+		var reader buf.Reader
+		if network == net.Network_UDP {
+			reader = &PacketReader{
+				Reader: conn, HyConn: hyConn,
+			}
+		} else {
+			ok, msg, err := hyProtocol.ReadTCPResponse(conn)
+			if err != nil {
+				return err
+			}
+			if !ok {
+				return newError(msg)
+			}
+			reader = buf.NewReader(conn)
+		}
+		return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
+	}
+
+	responseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer))
+	if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil {
+		return newError("connection ends").Base(err)
+	}
+
+	return nil
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewClient(ctx, config.(*ClientConfig))
+	}))
+}

+ 45 - 0
proxy/hysteria2/config.go

@@ -0,0 +1,45 @@
+package hysteria2
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/protocol"
+)
+
+// MemoryAccount is an account type converted from Account.
+type MemoryAccount struct {
+	Password string
+	Key      []byte
+}
+
+// AsAccount implements protocol.AsAccount.
+func (a *Account) AsAccount() (protocol.Account, error) {
+	return &MemoryAccount{}, nil
+}
+
+// Equals implements protocol.Account.Equals().
+func (a *MemoryAccount) Equals(another protocol.Account) bool {
+	if account, ok := another.(*MemoryAccount); ok {
+		return a.Password == account.Password
+	}
+	return false
+}
+
+func hexSha224(password string) []byte {
+	buf := make([]byte, 56)
+	hash := sha256.New224()
+	common.Must2(hash.Write([]byte(password)))
+	hex.Encode(buf, hash.Sum(nil))
+	return buf
+}
+
+func hexString(data []byte) string {
+	str := ""
+	for _, v := range data {
+		str += fmt.Sprintf("%02x", v)
+	}
+	return str
+}

+ 363 - 0
proxy/hysteria2/config.pb.go

@@ -0,0 +1,363 @@
+package hysteria2
+
+import (
+	protocol "github.com/v2fly/v2ray-core/v5/common/protocol"
+	_ "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 OBFS struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Type     string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+	Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+}
+
+func (x *OBFS) Reset() {
+	*x = OBFS{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_hysteria2_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OBFS) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OBFS) ProtoMessage() {}
+
+func (x *OBFS) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_hysteria2_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 OBFS.ProtoReflect.Descriptor instead.
+func (*OBFS) Descriptor() ([]byte, []int) {
+	return file_proxy_hysteria2_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *OBFS) GetType() string {
+	if x != nil {
+		return x.Type
+	}
+	return ""
+}
+
+func (x *OBFS) GetPassword() string {
+	if x != nil {
+		return x.Password
+	}
+	return ""
+}
+
+type Account struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Obfs *OBFS `protobuf:"bytes,1,opt,name=obfs,proto3" json:"obfs,omitempty"`
+}
+
+func (x *Account) Reset() {
+	*x = Account{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_hysteria2_config_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Account) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Account) ProtoMessage() {}
+
+func (x *Account) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_hysteria2_config_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 Account.ProtoReflect.Descriptor instead.
+func (*Account) Descriptor() ([]byte, []int) {
+	return file_proxy_hysteria2_config_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Account) GetObfs() *OBFS {
+	if x != nil {
+		return x.Obfs
+	}
+	return nil
+}
+
+type ClientConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
+}
+
+func (x *ClientConfig) Reset() {
+	*x = ClientConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_hysteria2_config_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ClientConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientConfig) ProtoMessage() {}
+
+func (x *ClientConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_hysteria2_config_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
+func (*ClientConfig) Descriptor() ([]byte, []int) {
+	return file_proxy_hysteria2_config_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
+	if x != nil {
+		return x.Server
+	}
+	return nil
+}
+
+type ServerConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
+}
+
+func (x *ServerConfig) Reset() {
+	*x = ServerConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_hysteria2_config_proto_msgTypes[3]
+		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_proxy_hysteria2_config_proto_msgTypes[3]
+	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_proxy_hysteria2_config_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *ServerConfig) GetUsers() []*protocol.User {
+	if x != nil {
+		return x.Users
+	}
+	return nil
+}
+
+var File_proxy_hysteria2_config_proto protoreflect.FileDescriptor
+
+var file_proxy_hysteria2_config_proto_rawDesc = []byte{
+	0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61,
+	0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a,
+	0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
+	0x2e, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73,
+	0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
+	0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e,
+	0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x04, 0x4f,
+	0x42, 0x46, 0x53, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
+	0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
+	0x6f, 0x72, 0x64, 0x22, 0x3f, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34,
+	0x0a, 0x04, 0x6f, 0x62, 0x66, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76,
+	0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
+	0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x2e, 0x4f, 0x42, 0x46, 0x53, 0x52, 0x04,
+	0x6f, 0x62, 0x66, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
+	0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
+	0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
+	0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x19, 0x82, 0xb5, 0x18, 0x15, 0x0a, 0x08,
+	0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x09, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72,
+	0x69, 0x61, 0x32, 0x22, 0x60, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x20, 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,
+	0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x3a, 0x18, 0x82, 0xb5, 0x18,
+	0x14, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x09, 0x68, 0x79, 0x73, 0x74,
+	0x65, 0x72, 0x69, 0x61, 0x32, 0x42, 0x6f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72,
+	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x79,
+	0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x50, 0x01, 0x5a, 0x2e, 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,
+	0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61,
+	0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48, 0x79, 0x73,
+	0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_proxy_hysteria2_config_proto_rawDescOnce sync.Once
+	file_proxy_hysteria2_config_proto_rawDescData = file_proxy_hysteria2_config_proto_rawDesc
+)
+
+func file_proxy_hysteria2_config_proto_rawDescGZIP() []byte {
+	file_proxy_hysteria2_config_proto_rawDescOnce.Do(func() {
+		file_proxy_hysteria2_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_hysteria2_config_proto_rawDescData)
+	})
+	return file_proxy_hysteria2_config_proto_rawDescData
+}
+
+var file_proxy_hysteria2_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_proxy_hysteria2_config_proto_goTypes = []interface{}{
+	(*OBFS)(nil),                    // 0: v2ray.core.proxy.hysteria2.OBFS
+	(*Account)(nil),                 // 1: v2ray.core.proxy.hysteria2.Account
+	(*ClientConfig)(nil),            // 2: v2ray.core.proxy.hysteria2.ClientConfig
+	(*ServerConfig)(nil),            // 3: v2ray.core.proxy.hysteria2.ServerConfig
+	(*protocol.ServerEndpoint)(nil), // 4: v2ray.core.common.protocol.ServerEndpoint
+	(*protocol.User)(nil),           // 5: v2ray.core.common.protocol.User
+}
+var file_proxy_hysteria2_config_proto_depIdxs = []int32{
+	0, // 0: v2ray.core.proxy.hysteria2.Account.obfs:type_name -> v2ray.core.proxy.hysteria2.OBFS
+	4, // 1: v2ray.core.proxy.hysteria2.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint
+	5, // 2: v2ray.core.proxy.hysteria2.ServerConfig.users:type_name -> v2ray.core.common.protocol.User
+	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
+}
+
+func init() { file_proxy_hysteria2_config_proto_init() }
+func file_proxy_hysteria2_config_proto_init() {
+	if File_proxy_hysteria2_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_proxy_hysteria2_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OBFS); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proxy_hysteria2_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Account); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proxy_hysteria2_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ClientConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proxy_hysteria2_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			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_proxy_hysteria2_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   4,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_proxy_hysteria2_config_proto_goTypes,
+		DependencyIndexes: file_proxy_hysteria2_config_proto_depIdxs,
+		MessageInfos:      file_proxy_hysteria2_config_proto_msgTypes,
+	}.Build()
+	File_proxy_hysteria2_config_proto = out.File
+	file_proxy_hysteria2_config_proto_rawDesc = nil
+	file_proxy_hysteria2_config_proto_goTypes = nil
+	file_proxy_hysteria2_config_proto_depIdxs = nil
+}

+ 34 - 0
proxy/hysteria2/config.proto

@@ -0,0 +1,34 @@
+syntax = "proto3";
+
+package v2ray.core.proxy.hysteria2;
+option csharp_namespace = "V2Ray.Core.Proxy.Hysteria2";
+option go_package = "github.com/v2fly/v2ray-core/v5/proxy/hysteria2";
+option java_package = "com.v2ray.core.proxy.hysteria2";
+option java_multiple_files = true;
+
+import "common/protocol/user.proto";
+import "common/protocol/server_spec.proto";
+import "common/protoext/extensions.proto";
+
+message OBFS {
+  string type = 1;
+  string password = 2;
+}
+
+message Account {
+  OBFS obfs = 1;
+}
+
+message ClientConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "outbound";
+  option (v2ray.core.common.protoext.message_opt).short_name = "hysteria2";
+
+  repeated v2ray.core.common.protocol.ServerEndpoint server = 1;
+}
+
+message ServerConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "inbound";
+  option (v2ray.core.common.protoext.message_opt).short_name = "hysteria2";
+
+  repeated v2ray.core.common.protocol.User users = 1;
+}

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

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

+ 1 - 0
proxy/hysteria2/hysteria2.go

@@ -0,0 +1 @@
+package hysteria2

+ 204 - 0
proxy/hysteria2/protocol.go

@@ -0,0 +1,204 @@
+package hysteria2
+
+import (
+	"io"
+	gonet "net"
+
+	hyProtocol "github.com/apernet/hysteria/core/v2/international/protocol"
+	"github.com/apernet/quic-go/quicvarint"
+
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
+)
+
+// ConnWriter is TCP Connection Writer Wrapper
+type ConnWriter struct {
+	io.Writer
+	Target        net.Destination
+	Account       *MemoryAccount
+	TCPHeaderSent bool
+}
+
+// Write implements io.Writer
+func (c *ConnWriter) Write(p []byte) (n int, err error) {
+	if !c.TCPHeaderSent {
+		if err := c.writeTCPHeader(); err != nil {
+			return 0, newError("failed to write request header").Base(err)
+		}
+	}
+
+	return c.Writer.Write(p)
+}
+
+// WriteMultiBuffer implements buf.Writer
+func (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
+	defer buf.ReleaseMulti(mb)
+
+	for _, b := range mb {
+		if !b.IsEmpty() {
+			if _, err := c.Write(b.Bytes()); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func (c *ConnWriter) WriteTCPHeader() error {
+	if !c.TCPHeaderSent {
+		if err := c.writeTCPHeader(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func QuicLen(s int) int {
+	return int(quicvarint.Len(uint64(s)))
+}
+
+func (c *ConnWriter) writeTCPHeader() error {
+	c.TCPHeaderSent = true
+
+	// TODO: the padding length here should be randomized
+
+	padding := "Jimmy Was Here"
+	paddingLen := len(padding)
+	addressAndPort := c.Target.NetAddr()
+	addressLen := len(addressAndPort)
+	size := QuicLen(addressLen) + addressLen + QuicLen(paddingLen) + paddingLen
+
+	if size > hyProtocol.MaxAddressLength+hyProtocol.MaxPaddingLength {
+		return newError("invalid header length")
+	}
+
+	buf := make([]byte, size)
+	i := hyProtocol.VarintPut(buf, uint64(addressLen))
+	i += copy(buf[i:], addressAndPort)
+	i += hyProtocol.VarintPut(buf[i:], uint64(paddingLen))
+	copy(buf[i:], padding)
+
+	_, err := c.Writer.Write(buf)
+	return err
+}
+
+// PacketWriter UDP Connection Writer Wrapper
+type PacketWriter struct {
+	io.Writer
+	HyConn *hyTransport.HyConn
+	Target net.Destination
+}
+
+// WriteMultiBuffer implements buf.Writer
+func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
+	for _, b := range mb {
+		if b.IsEmpty() {
+			continue
+		}
+		if _, err := w.writePacket(b.Bytes(), w.Target); err != nil {
+			buf.ReleaseMulti(mb)
+			return err
+		}
+	}
+
+	return nil
+}
+
+// WriteMultiBufferWithMetadata writes udp packet with destination specified
+func (w *PacketWriter) WriteMultiBufferWithMetadata(mb buf.MultiBuffer, dest net.Destination) error {
+	for _, b := range mb {
+		if b.IsEmpty() {
+			continue
+		}
+		if _, err := w.writePacket(b.Bytes(), dest); err != nil {
+			buf.ReleaseMulti(mb)
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (w *PacketWriter) WriteTo(payload []byte, addr gonet.Addr) (int, error) {
+	dest := net.DestinationFromAddr(addr)
+
+	return w.writePacket(payload, dest)
+}
+
+func (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) {
+	return w.HyConn.WritePacket(payload, dest)
+}
+
+// ConnReader is TCP Connection Reader Wrapper
+type ConnReader struct {
+	io.Reader
+	Target net.Destination
+}
+
+// Read implements io.Reader
+func (c *ConnReader) Read(p []byte) (int, error) {
+	return c.Reader.Read(p)
+}
+
+// ReadMultiBuffer implements buf.Reader
+func (c *ConnReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
+	b := buf.New()
+	_, err := b.ReadFrom(c)
+	return buf.MultiBuffer{b}, err
+}
+
+// PacketPayload combines udp payload and destination
+type PacketPayload struct {
+	Target net.Destination
+	Buffer buf.MultiBuffer
+}
+
+// PacketReader is UDP Connection Reader Wrapper
+type PacketReader struct {
+	io.Reader
+	HyConn *hyTransport.HyConn
+}
+
+// ReadMultiBuffer implements buf.Reader
+func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
+	p, err := r.ReadMultiBufferWithMetadata()
+	if p != nil {
+		return p.Buffer, err
+	}
+	return nil, err
+}
+
+// ReadMultiBufferWithMetadata reads udp packet with destination
+func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) {
+	_, data, dest, err := r.HyConn.ReadPacket()
+	if err != nil {
+		return nil, err
+	}
+	b := buf.FromBytes(data)
+	return &PacketPayload{Target: *dest, Buffer: buf.MultiBuffer{b}}, nil
+}
+
+type PacketConnectionReader struct {
+	reader  *PacketReader
+	payload *PacketPayload
+}
+
+func (r *PacketConnectionReader) ReadFrom(p []byte) (n int, addr gonet.Addr, err error) {
+	if r.payload == nil || r.payload.Buffer.IsEmpty() {
+		r.payload, err = r.reader.ReadMultiBufferWithMetadata()
+		if err != nil {
+			return
+		}
+	}
+
+	addr = &gonet.UDPAddr{
+		IP:   r.payload.Target.Address.IP(),
+		Port: int(r.payload.Target.Port),
+	}
+
+	r.payload.Buffer, n = buf.SplitFirstBytes(r.payload.Buffer, p)
+
+	return
+}

+ 216 - 0
proxy/hysteria2/server.go

@@ -0,0 +1,216 @@
+package hysteria2
+
+import (
+	"context"
+	"io"
+	"time"
+
+	hyProtocol "github.com/apernet/hysteria/core/v2/international/protocol"
+
+	core "github.com/v2fly/v2ray-core/v5"
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/errors"
+	"github.com/v2fly/v2ray-core/v5/common/log"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
+	udp_proto "github.com/v2fly/v2ray-core/v5/common/protocol/udp"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/common/signal"
+	"github.com/v2fly/v2ray-core/v5/common/task"
+	"github.com/v2fly/v2ray-core/v5/features/policy"
+	"github.com/v2fly/v2ray-core/v5/features/routing"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/udp"
+)
+
+func init() {
+	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewServer(ctx, config.(*ServerConfig))
+	}))
+}
+
+// Server is an inbound connection handler that handles messages in protocol.
+type Server struct {
+	policyManager  policy.Manager
+	packetEncoding packetaddr.PacketAddrType
+}
+
+// NewServer creates a new inbound handler.
+func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
+	v := core.MustFromContext(ctx)
+	server := &Server{
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
+	}
+	return server, nil
+}
+
+// Network implements proxy.Inbound.Network().
+func (s *Server) Network() []net.Network {
+	return []net.Network{net.Network_TCP, net.Network_UNIX}
+}
+
+// Process implements proxy.Inbound.Process().
+func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
+	sid := session.ExportIDToError(ctx)
+
+	iConn := conn
+	if statConn, ok := conn.(*internet.StatCouterConnection); ok {
+		iConn = statConn.Connection // will not count the UDP traffic.
+	}
+	hyConn, IsHy2Transport := iConn.(*hyTransport.HyConn)
+
+	if IsHy2Transport && hyConn.IsUDPExtension {
+		network = net.Network_UDP
+	}
+
+	if !IsHy2Transport && network == net.Network_UDP {
+		return newError(hyTransport.CanNotUseUdpExtension)
+	}
+
+	sessionPolicy := s.policyManager.ForLevel(0)
+	if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
+		return newError("unable to set read deadline").Base(err).AtWarning()
+	}
+
+	bufferedReader := &buf.BufferedReader{
+		Reader: buf.NewReader(conn),
+	}
+	clientReader := &ConnReader{Reader: bufferedReader}
+
+	if err := conn.SetReadDeadline(time.Time{}); err != nil {
+		return newError("unable to set read deadline").Base(err).AtWarning()
+	}
+
+	if network == net.Network_UDP { // handle udp request
+		return s.handleUDPPayload(ctx,
+			&PacketReader{Reader: clientReader, HyConn: hyConn},
+			&PacketWriter{Writer: conn, HyConn: hyConn}, dispatcher)
+	}
+
+	var reqAddr string
+	var err error
+	reqAddr, err = hyProtocol.ReadTCPRequest(conn)
+	if err != nil {
+		return newError("failed to parse header").Base(err)
+	}
+	err = hyProtocol.WriteTCPResponse(conn, true, "")
+	if err != nil {
+		return newError("failed to send response").Base(err)
+	}
+
+	address, stringPort, err := net.SplitHostPort(reqAddr)
+	if err != nil {
+		return err
+	}
+	port, err := net.PortFromString(stringPort)
+	if err != nil {
+		return err
+	}
+	destination := net.Destination{Network: network, Address: net.ParseAddress(address), Port: port}
+
+	inbound := session.InboundFromContext(ctx)
+	if inbound == nil {
+		panic("no inbound metadata")
+	}
+	sessionPolicy = s.policyManager.ForLevel(0)
+
+	ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
+		From:   conn.RemoteAddr(),
+		To:     destination,
+		Status: log.AccessAccepted,
+		Reason: "",
+	})
+
+	newError("received request for ", destination).WriteToLog(sid)
+	return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher)
+}
+
+func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session,
+	destination net.Destination,
+	clientReader buf.Reader,
+	clientWriter buf.Writer, dispatcher routing.Dispatcher,
+) error {
+	ctx, cancel := context.WithCancel(ctx)
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
+	ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
+
+	link, err := dispatcher.Dispatch(ctx, destination)
+	if err != nil {
+		return newError("failed to dispatch request to ", destination).Base(err)
+	}
+
+	requestDone := func() error {
+		defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
+
+		if err := buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)); err != nil {
+			return newError("failed to transfer request").Base(err)
+		}
+		return nil
+	}
+
+	responseDone := func() error {
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
+
+		if err := buf.Copy(link.Reader, clientWriter, buf.UpdateActivity(timer)); err != nil {
+			return newError("failed to write response").Base(err)
+		}
+		return nil
+	}
+
+	requestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))
+	if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
+		common.Must(common.Interrupt(link.Reader))
+		common.Must(common.Interrupt(link.Writer))
+		return newError("connection ends").Base(err)
+	}
+
+	return nil
+}
+
+func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error { // {{{
+	udpDispatcherConstructor := udp.NewSplitDispatcher
+	switch s.packetEncoding {
+	case packetaddr.PacketAddrType_None:
+	case packetaddr.PacketAddrType_Packet:
+		packetAddrDispatcherFactory := udp.NewPacketAddrDispatcherCreator(ctx)
+		udpDispatcherConstructor = packetAddrDispatcherFactory.NewPacketAddrDispatcher
+	}
+
+	udpServer := udpDispatcherConstructor(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
+		if err := clientWriter.WriteMultiBufferWithMetadata(buf.MultiBuffer{packet.Payload}, packet.Source); err != nil {
+			newError("failed to write response").Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
+		}
+	})
+
+	inbound := session.InboundFromContext(ctx)
+	// user := inbound.User
+
+	for {
+		select {
+		case <-ctx.Done():
+			return nil
+		default:
+			p, err := clientReader.ReadMultiBufferWithMetadata()
+			if err != nil {
+				if errors.Cause(err) != io.EOF {
+					return newError("unexpected EOF").Base(err)
+				}
+				return nil
+			}
+			currentPacketCtx := ctx
+			currentPacketCtx = log.ContextWithAccessMessage(currentPacketCtx, &log.AccessMessage{
+				From:   inbound.Source,
+				To:     p.Target,
+				Status: log.AccessAccepted,
+				Reason: "",
+			})
+			newError("tunnelling request to ", p.Target).WriteToLog(session.ExportIDToError(ctx))
+
+			for _, b := range p.Buffer {
+				udpServer.Dispatch(currentPacketCtx, p.Target, b)
+			}
+		}
+	}
+} // }}}

+ 480 - 0
testing/scenarios/hy2_test.go

@@ -0,0 +1,480 @@
+package scenarios
+
+import (
+	"testing"
+	"time"
+
+	"golang.org/x/sync/errgroup"
+	"google.golang.org/protobuf/types/known/anypb"
+
+	core "github.com/v2fly/v2ray-core/v5"
+	"github.com/v2fly/v2ray-core/v5/app/log"
+	"github.com/v2fly/v2ray-core/v5/app/proxyman"
+	"github.com/v2fly/v2ray-core/v5/common"
+	clog "github.com/v2fly/v2ray-core/v5/common/log"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/protocol"
+	"github.com/v2fly/v2ray-core/v5/common/protocol/tls/cert"
+	"github.com/v2fly/v2ray-core/v5/common/serial"
+	"github.com/v2fly/v2ray-core/v5/common/uuid"
+	"github.com/v2fly/v2ray-core/v5/proxy/dokodemo"
+	"github.com/v2fly/v2ray-core/v5/proxy/freedom"
+	"github.com/v2fly/v2ray-core/v5/proxy/hysteria2"
+	"github.com/v2fly/v2ray-core/v5/proxy/vmess"
+	"github.com/v2fly/v2ray-core/v5/proxy/vmess/inbound"
+	"github.com/v2fly/v2ray-core/v5/proxy/vmess/outbound"
+	"github.com/v2fly/v2ray-core/v5/testing/servers/tcp"
+	"github.com/v2fly/v2ray-core/v5/testing/servers/udp"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
+	hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
+	tcpTransport "github.com/v2fly/v2ray-core/v5/transport/internet/tcp"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
+)
+
+func TestVMessHysteria2Congestion(t *testing.T) {
+	for _, v := range []string{"bbr", "brutal"} {
+		testVMessHysteria2(t, v)
+	}
+}
+
+func testVMessHysteria2(t *testing.T, congestionType string) {
+	tcpServer := tcp.Server{
+		MsgProcessor: xor,
+	}
+	dest, err := tcpServer.Start()
+	common.Must(err)
+	defer tcpServer.Close()
+
+	userID := protocol.NewID(uuid.New())
+	serverPort := udp.PickPort()
+	serverConfig := &core.Config{
+		App: []*anypb.Any{
+			serial.ToTypedMessage(&log.Config{
+				Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(serverPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+
+					StreamSettings: &internet.StreamConfig{
+						ProtocolName: "hysteria2",
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*anypb.Any{
+							serial.ToTypedMessage(
+								&tls.Config{
+									Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
+								},
+							),
+						},
+						TransportSettings: []*internet.TransportConfig{
+							{
+								ProtocolName: "hysteria2",
+								Settings: serial.ToTypedMessage(&hyTransport.Config{
+									Congestion: &hyTransport.Congestion{Type: congestionType, UpMbps: 100, DownMbps: 100},
+									Password:   "password",
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
+					User: []*protocol.User{
+						{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id:      userID.String(),
+								AlterId: 0,
+							}),
+						},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+			},
+		},
+	}
+
+	clientPort := tcp.PickPort()
+	clientConfig := &core.Config{
+		App: []*anypb.Any{
+			serial.ToTypedMessage(&log.Config{
+				Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(clientPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
+					Address: net.NewIPOrDomain(dest.Address),
+					Port:    uint32(dest.Port),
+					NetworkList: &net.NetworkList{
+						Network: []net.Network{net.Network_TCP},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						ProtocolName: "hysteria2",
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*anypb.Any{
+							serial.ToTypedMessage(
+								&tls.Config{
+									ServerName:    "www.v2fly.org",
+									AllowInsecure: true,
+								},
+							),
+						},
+						TransportSettings: []*internet.TransportConfig{
+							{
+								ProtocolName: "hysteria2",
+								Settings: serial.ToTypedMessage(&hyTransport.Config{
+									Congestion: &hyTransport.Congestion{Type: congestionType, UpMbps: 100, DownMbps: 100},
+									Password:   "password",
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
+					Receiver: []*protocol.ServerEndpoint{
+						{
+							Address: net.NewIPOrDomain(net.LocalHostIP),
+							Port:    uint32(serverPort),
+							User: []*protocol.User{
+								{
+									Account: serial.ToTypedMessage(&vmess.Account{
+										Id:      userID.String(),
+										AlterId: 0,
+										SecuritySettings: &protocol.SecurityConfig{
+											Type: protocol.SecurityType_NONE,
+										},
+									}),
+								},
+							},
+						},
+					},
+				}),
+			},
+		},
+	}
+
+	servers, err := InitializeServerConfigs(serverConfig, clientConfig)
+	if err != nil {
+		t.Fatal("Failed to initialize all servers: ", err.Error())
+	}
+	defer CloseAllServers(servers)
+
+	var errg errgroup.Group
+	for i := 0; i < 10; i++ {
+		errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
+	}
+
+	if err := errg.Wait(); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestHysteria2Offical(t *testing.T) {
+	for _, v := range []bool{true, false} {
+		testHysteria2Offical(t, v)
+	}
+}
+
+func testHysteria2Offical(t *testing.T, isUDP bool) {
+	var dest net.Destination
+	var err error
+	if isUDP {
+		udpServer := udp.Server{
+			MsgProcessor: xor,
+		}
+		dest, err = udpServer.Start()
+		common.Must(err)
+		defer udpServer.Close()
+	} else {
+		tcpServer := tcp.Server{
+			MsgProcessor: xor,
+		}
+		dest, err = tcpServer.Start()
+		common.Must(err)
+		defer tcpServer.Close()
+	}
+
+	serverPort := udp.PickPort()
+	serverConfig := &core.Config{
+		App: []*anypb.Any{
+			serial.ToTypedMessage(&log.Config{
+				Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(serverPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						ProtocolName: "hysteria2",
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*anypb.Any{
+							serial.ToTypedMessage(
+								&tls.Config{
+									Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
+								},
+							),
+						},
+						TransportSettings: []*internet.TransportConfig{
+							{
+								ProtocolName: "hysteria2",
+								Settings: serial.ToTypedMessage(&hyTransport.Config{
+									Congestion:      &hyTransport.Congestion{Type: "brutal", UpMbps: 100, DownMbps: 100},
+									UseUdpExtension: true,
+									Password:        "password",
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&hysteria2.ServerConfig{
+					Users: []*protocol.User{
+						{
+							Account: serial.ToTypedMessage(&hysteria2.Account{}),
+						},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+			},
+		},
+	}
+
+	clientPort := tcp.PickPort()
+	clientConfig := &core.Config{
+		App: []*anypb.Any{
+			serial.ToTypedMessage(&log.Config{
+				Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(clientPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
+					Address: net.NewIPOrDomain(dest.Address),
+					Port:    uint32(dest.Port),
+					NetworkList: &net.NetworkList{
+						Network: []net.Network{net.Network_TCP, net.Network_UDP},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						ProtocolName: "hysteria2",
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*anypb.Any{
+							serial.ToTypedMessage(
+								&tls.Config{
+									ServerName:    "www.v2fly.org",
+									AllowInsecure: true,
+								},
+							),
+						},
+						TransportSettings: []*internet.TransportConfig{
+							{
+								ProtocolName: "hysteria2",
+								Settings: serial.ToTypedMessage(&hyTransport.Config{
+									Congestion:      &hyTransport.Congestion{Type: "brutal", UpMbps: 100, DownMbps: 100},
+									UseUdpExtension: true,
+									Password:        "password",
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&hysteria2.ClientConfig{
+					Server: []*protocol.ServerEndpoint{
+						{
+							Address: net.NewIPOrDomain(net.LocalHostIP),
+							Port:    uint32(serverPort),
+							User: []*protocol.User{
+								{
+									Account: serial.ToTypedMessage(&hysteria2.Account{}),
+								},
+							},
+						},
+					},
+				}),
+			},
+		},
+	}
+
+	servers, err := InitializeServerConfigs(serverConfig, clientConfig)
+	if err != nil {
+		t.Fatal("Failed to initialize all servers: ", err.Error())
+	}
+	defer CloseAllServers(servers)
+
+	var errg errgroup.Group
+	for i := 0; i < 10; i++ {
+		if isUDP {
+			errg.Go(testUDPConn(clientPort, 1024, time.Second*4))
+		} else {
+			errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
+		}
+	}
+
+	if err := errg.Wait(); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestHysteria2OnTCP(t *testing.T) {
+	tcpServer := tcp.Server{
+		MsgProcessor: xor,
+	}
+	dest, err := tcpServer.Start()
+	common.Must(err)
+	defer tcpServer.Close()
+
+	serverPort := udp.PickPort()
+	serverConfig := &core.Config{
+		App: []*anypb.Any{
+			serial.ToTypedMessage(&log.Config{
+				Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(serverPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*anypb.Any{
+							serial.ToTypedMessage(
+								&tls.Config{
+									Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
+								},
+							),
+						},
+						TransportSettings: []*internet.TransportConfig{
+							{
+								Protocol: internet.TransportProtocol_TCP,
+								Settings: serial.ToTypedMessage(&tcpTransport.Config{
+									HeaderSettings: serial.ToTypedMessage(&http.Config{}),
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&hysteria2.ServerConfig{
+					Users: []*protocol.User{
+						{
+							Account: serial.ToTypedMessage(&hysteria2.Account{}),
+						},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+			},
+		},
+	}
+
+	clientPort := tcp.PickPort()
+	clientConfig := &core.Config{
+		App: []*anypb.Any{
+			serial.ToTypedMessage(&log.Config{
+				Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(clientPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
+					Address: net.NewIPOrDomain(dest.Address),
+					Port:    uint32(dest.Port),
+					NetworkList: &net.NetworkList{
+						Network: []net.Network{net.Network_TCP},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*anypb.Any{
+							serial.ToTypedMessage(
+								&tls.Config{
+									ServerName:    "www.v2fly.org",
+									AllowInsecure: true,
+								},
+							),
+						},
+						TransportSettings: []*internet.TransportConfig{
+							{
+								Protocol: internet.TransportProtocol_TCP,
+								Settings: serial.ToTypedMessage(&tcpTransport.Config{
+									HeaderSettings: serial.ToTypedMessage(&http.Config{}),
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&hysteria2.ClientConfig{
+					Server: []*protocol.ServerEndpoint{
+						{
+							Address: net.NewIPOrDomain(net.LocalHostIP),
+							Port:    uint32(serverPort),
+							User: []*protocol.User{
+								{
+									Account: serial.ToTypedMessage(&hysteria2.Account{}),
+								},
+							},
+						},
+					},
+				}),
+			},
+		},
+	}
+
+	servers, err := InitializeServerConfigs(serverConfig, clientConfig)
+	if err != nil {
+		t.Fatal("Failed to initialize all servers: ", err.Error())
+	}
+	defer CloseAllServers(servers)
+
+	var errg errgroup.Group
+	for i := 0; i < 1; i++ {
+		errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
+	}
+
+	if err := errg.Wait(); err != nil {
+		t.Error(err)
+	}
+}

+ 4 - 2
transport/internet/grpc/dial.go

@@ -13,6 +13,7 @@ import (
 	"google.golang.org/grpc/backoff"
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/credentials/insecure"
 
 	core "github.com/v2fly/v2ray-core/v5"
 	"github.com/v2fly/v2ray-core/v5/common"
@@ -48,11 +49,12 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
 	grpcSettings := streamSettings.ProtocolSettings.(*Config)
 
 	config := tls.ConfigFromStreamSettings(streamSettings)
-	dialOption := grpc.WithInsecure()
 
+	transportCredentials := insecure.NewCredentials()
 	if config != nil {
-		dialOption = grpc.WithTransportCredentials(credentials.NewTLS(config.GetTLSConfig()))
+		transportCredentials = credentials.NewTLS(config.GetTLSConfig())
 	}
+	dialOption := grpc.WithTransportCredentials(transportCredentials)
 
 	conn, canceller, err := getGrpcClient(ctx, dest, dialOption, streamSettings)
 	if err != nil {

+ 298 - 0
transport/internet/hysteria2/config.pb.go

@@ -0,0 +1,298 @@
+package hysteria2
+
+import (
+	protocol "github.com/v2fly/v2ray-core/v5/common/protocol"
+	_ "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 Congestion struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Type     string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+	UpMbps   uint64 `protobuf:"varint,2,opt,name=up_mbps,json=upMbps,proto3" json:"up_mbps,omitempty"`
+	DownMbps uint64 `protobuf:"varint,3,opt,name=down_mbps,json=downMbps,proto3" json:"down_mbps,omitempty"`
+}
+
+func (x *Congestion) Reset() {
+	*x = Congestion{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_hysteria2_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Congestion) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Congestion) ProtoMessage() {}
+
+func (x *Congestion) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_hysteria2_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 Congestion.ProtoReflect.Descriptor instead.
+func (*Congestion) Descriptor() ([]byte, []int) {
+	return file_transport_internet_hysteria2_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Congestion) GetType() string {
+	if x != nil {
+		return x.Type
+	}
+	return ""
+}
+
+func (x *Congestion) GetUpMbps() uint64 {
+	if x != nil {
+		return x.UpMbps
+	}
+	return 0
+}
+
+func (x *Congestion) GetDownMbps() uint64 {
+	if x != nil {
+		return x.DownMbps
+	}
+	return 0
+}
+
+type Config struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Key                   string                   `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	Security              *protocol.SecurityConfig `protobuf:"bytes,2,opt,name=security,proto3" json:"security,omitempty"`
+	Password              string                   `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
+	Congestion            *Congestion              `protobuf:"bytes,4,opt,name=congestion,proto3" json:"congestion,omitempty"`
+	IgnoreClientBandwidth bool                     `protobuf:"varint,5,opt,name=ignore_client_bandwidth,json=ignoreClientBandwidth,proto3" json:"ignore_client_bandwidth,omitempty"`
+	UseUdpExtension       bool                     `protobuf:"varint,6,opt,name=use_udp_extension,json=useUdpExtension,proto3" json:"use_udp_extension,omitempty"`
+}
+
+func (x *Config) Reset() {
+	*x = Config{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_hysteria2_config_proto_msgTypes[1]
+		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_hysteria2_config_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 Config.ProtoReflect.Descriptor instead.
+func (*Config) Descriptor() ([]byte, []int) {
+	return file_transport_internet_hysteria2_config_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Config) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *Config) GetSecurity() *protocol.SecurityConfig {
+	if x != nil {
+		return x.Security
+	}
+	return nil
+}
+
+func (x *Config) GetPassword() string {
+	if x != nil {
+		return x.Password
+	}
+	return ""
+}
+
+func (x *Config) GetCongestion() *Congestion {
+	if x != nil {
+		return x.Congestion
+	}
+	return nil
+}
+
+func (x *Config) GetIgnoreClientBandwidth() bool {
+	if x != nil {
+		return x.IgnoreClientBandwidth
+	}
+	return false
+}
+
+func (x *Config) GetUseUdpExtension() bool {
+	if x != nil {
+		return x.UseUdpExtension
+	}
+	return false
+}
+
+var File_transport_internet_hysteria2_config_proto protoreflect.FileDescriptor
+
+var file_transport_internet_hysteria2_config_proto_rawDesc = []byte{
+	0x0a, 0x29, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x2f, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 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, 0x68, 0x79, 0x73, 0x74, 0x65,
+	0x72, 0x69, 0x61, 0x32, 0x1a, 0x1d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x70, 0x5f, 0x6d, 0x62,
+	0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x70, 0x4d, 0x62, 0x70, 0x73,
+	0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x6d, 0x62, 0x70, 0x73, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x62, 0x70, 0x73, 0x22, 0xd3, 0x02,
+	0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x08, 0x73, 0x65,
+	0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76,
+	0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69,
+	0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69,
+	0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x53,
+	0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x33, 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, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x2e, 0x43, 0x6f, 0x6e,
+	0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x63, 0x6c,
+	0x69, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x43, 0x6c, 0x69, 0x65,
+	0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x75,
+	0x73, 0x65, 0x5f, 0x75, 0x64, 0x70, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
+	0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x55, 0x64, 0x70, 0x45, 0x78,
+	0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x1a, 0x82, 0xb5, 0x18, 0x16, 0x0a, 0x09, 0x74,
+	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x09, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72,
+	0x69, 0x61, 0x32, 0x42, 0x96, 0x01, 0x0a, 0x2b, 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, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72,
+	0x69, 0x61, 0x32, 0x50, 0x01, 0x5a, 0x3b, 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, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69,
+	0x61, 0x32, 0xaa, 0x02, 0x27, 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, 0x48, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_transport_internet_hysteria2_config_proto_rawDescOnce sync.Once
+	file_transport_internet_hysteria2_config_proto_rawDescData = file_transport_internet_hysteria2_config_proto_rawDesc
+)
+
+func file_transport_internet_hysteria2_config_proto_rawDescGZIP() []byte {
+	file_transport_internet_hysteria2_config_proto_rawDescOnce.Do(func() {
+		file_transport_internet_hysteria2_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_hysteria2_config_proto_rawDescData)
+	})
+	return file_transport_internet_hysteria2_config_proto_rawDescData
+}
+
+var file_transport_internet_hysteria2_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_transport_internet_hysteria2_config_proto_goTypes = []interface{}{
+	(*Congestion)(nil),              // 0: v2ray.core.transport.internet.hysteria2.Congestion
+	(*Config)(nil),                  // 1: v2ray.core.transport.internet.hysteria2.Config
+	(*protocol.SecurityConfig)(nil), // 2: v2ray.core.common.protocol.SecurityConfig
+}
+var file_transport_internet_hysteria2_config_proto_depIdxs = []int32{
+	2, // 0: v2ray.core.transport.internet.hysteria2.Config.security:type_name -> v2ray.core.common.protocol.SecurityConfig
+	0, // 1: v2ray.core.transport.internet.hysteria2.Config.congestion:type_name -> v2ray.core.transport.internet.hysteria2.Congestion
+	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_hysteria2_config_proto_init() }
+func file_transport_internet_hysteria2_config_proto_init() {
+	if File_transport_internet_hysteria2_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_transport_internet_hysteria2_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Congestion); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_transport_internet_hysteria2_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Config); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_transport_internet_hysteria2_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_transport_internet_hysteria2_config_proto_goTypes,
+		DependencyIndexes: file_transport_internet_hysteria2_config_proto_depIdxs,
+		MessageInfos:      file_transport_internet_hysteria2_config_proto_msgTypes,
+	}.Build()
+	File_transport_internet_hysteria2_config_proto = out.File
+	file_transport_internet_hysteria2_config_proto_rawDesc = nil
+	file_transport_internet_hysteria2_config_proto_goTypes = nil
+	file_transport_internet_hysteria2_config_proto_depIdxs = nil
+}

+ 30 - 0
transport/internet/hysteria2/config.proto

@@ -0,0 +1,30 @@
+syntax = "proto3";
+
+package v2ray.core.transport.internet.hysteria2;
+option csharp_namespace = "V2Ray.Core.Transport.Internet.Hysteria2";
+option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2";
+option java_package = "com.v2ray.core.transport.internet.hysteria2";
+option java_multiple_files = true;
+
+import "common/protocol/headers.proto";
+
+import "common/protoext/extensions.proto";
+
+message Congestion{
+  string type = 1;
+
+  uint64 up_mbps = 2;
+  uint64 down_mbps = 3;
+}
+
+message Config {
+  option (v2ray.core.common.protoext.message_opt).type = "transport";
+  option (v2ray.core.common.protoext.message_opt).short_name = "hysteria2";
+
+  string key = 1;
+  v2ray.core.common.protocol.SecurityConfig security = 2;
+  string password = 3;
+  Congestion congestion = 4;
+  bool ignore_client_bandwidth = 5;
+  bool use_udp_extension = 6;
+}

+ 136 - 0
transport/internet/hysteria2/conn.go

@@ -0,0 +1,136 @@
+package hysteria2
+
+import (
+	"time"
+
+	hyClient "github.com/apernet/hysteria/core/v2/client"
+	"github.com/apernet/hysteria/core/v2/international/protocol"
+	hyServer "github.com/apernet/hysteria/core/v2/server"
+	"github.com/apernet/quic-go"
+
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+)
+
+const CanNotUseUdpExtension = "Only hysteria2 proxy protocol can use udpExtension."
+const Hy2MustNeedTLS = "Hysteria2 based on QUIC that requires TLS."
+
+type HyConn struct {
+	IsUDPExtension   bool
+	IsServer         bool
+	ClientUDPSession hyClient.HyUDPConn
+	ServerUDPSession *hyServer.UdpSessionEntry
+	Target           net.Destination
+
+	stream quic.Stream
+	local  net.Addr
+	remote net.Addr
+}
+
+func (c *HyConn) Read(b []byte) (int, error) {
+	if c.IsUDPExtension {
+		n, data, _, err := c.ReadPacket()
+		copy(b, data)
+		return n, err
+	}
+	return c.stream.Read(b)
+}
+
+func (c *HyConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
+	mb = buf.Compact(mb)
+	mb, err := buf.WriteMultiBuffer(c, mb)
+	buf.ReleaseMulti(mb)
+	return err
+}
+
+func (c *HyConn) Write(b []byte) (int, error) {
+	if c.IsUDPExtension {
+		dest, _ := net.ParseDestination("udp:v2fly.org:6666")
+		return c.WritePacket(b, dest)
+	}
+	return c.stream.Write(b)
+}
+
+func (c *HyConn) WritePacket(b []byte, dest net.Destination) (int, error) {
+	if !c.IsUDPExtension {
+		return 0, newError(CanNotUseUdpExtension)
+	}
+
+	if c.IsServer {
+		msg := &protocol.UDPMessage{
+			SessionID: c.ServerUDPSession.ID,
+			PacketID:  0,
+			FragID:    0,
+			FragCount: 1,
+			Addr:      dest.NetAddr(),
+			Data:      b,
+		}
+		c.ServerUDPSession.SendCh <- msg
+		return len(b), nil
+	}
+	return len(b), c.ClientUDPSession.Send(b, dest.NetAddr())
+}
+
+func (c *HyConn) ReadPacket() (int, []byte, *net.Destination, error) {
+	if !c.IsUDPExtension {
+		return 0, nil, nil, newError(CanNotUseUdpExtension)
+	}
+
+	if c.IsServer {
+		msg := <-c.ServerUDPSession.ReceiveCh
+		dest, err := net.ParseDestination("udp:" + msg.Addr)
+		return len(msg.Data), msg.Data, &dest, err
+	}
+	data, address, err := c.ClientUDPSession.Receive()
+	if err != nil {
+		return 0, nil, nil, err
+	}
+	dest, err := net.ParseDestination("udp:" + address)
+	if err != nil {
+		return 0, nil, nil, err
+	}
+	return len(data), data, &dest, nil
+}
+
+func (c *HyConn) Close() error {
+	if c.IsUDPExtension {
+		if !c.IsServer && c.ClientUDPSession == nil || (c.IsServer && c.ServerUDPSession == nil) {
+			return newError(CanNotUseUdpExtension)
+		}
+		if c.IsServer {
+			c.ServerUDPSession.Close()
+			return c.ServerUDPSession.Conn.Close()
+		}
+		return c.ClientUDPSession.Close()
+	}
+	return c.stream.Close()
+}
+
+func (c *HyConn) LocalAddr() net.Addr {
+	return c.local
+}
+
+func (c *HyConn) RemoteAddr() net.Addr {
+	return c.remote
+}
+
+func (c *HyConn) SetDeadline(t time.Time) error {
+	if c.IsUDPExtension {
+		return nil
+	}
+	return c.stream.SetDeadline(t)
+}
+
+func (c *HyConn) SetReadDeadline(t time.Time) error {
+	if c.IsUDPExtension {
+		return nil
+	}
+	return c.stream.SetReadDeadline(t)
+}
+
+func (c *HyConn) SetWriteDeadline(t time.Time) error {
+	if c.IsUDPExtension {
+		return nil
+	}
+	return c.stream.SetWriteDeadline(t)
+}

+ 199 - 0
transport/internet/hysteria2/dialer.go

@@ -0,0 +1,199 @@
+package hysteria2
+
+import (
+	"context"
+	"sync"
+
+	hyClient "github.com/apernet/hysteria/core/v2/client"
+	hyProtocol "github.com/apernet/hysteria/core/v2/international/protocol"
+	"github.com/apernet/quic-go/quicvarint"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
+)
+
+var RunningClient map[net.Addr](hyClient.Client)
+var ClientMutex sync.Mutex
+var MBps uint64 = 1000000 / 8 // MByte
+
+func GetClientTLSConfig(streamSettings *internet.MemoryStreamConfig) (*hyClient.TLSConfig, error) {
+	config := tls.ConfigFromStreamSettings(streamSettings)
+	if config == nil {
+		return nil, newError(Hy2MustNeedTLS)
+	}
+	tlsConfig := config.GetTLSConfig()
+
+	return &hyClient.TLSConfig{
+		RootCAs:               tlsConfig.RootCAs,
+		ServerName:            tlsConfig.ServerName,
+		InsecureSkipVerify:    tlsConfig.InsecureSkipVerify,
+		VerifyPeerCertificate: tlsConfig.VerifyPeerCertificate,
+	}, nil
+}
+
+func ResolveAddress(dest net.Destination) (net.Addr, error) {
+	var destAddr *net.UDPAddr
+	if dest.Address.Family().IsIP() {
+		destAddr = &net.UDPAddr{
+			IP:   dest.Address.IP(),
+			Port: int(dest.Port),
+		}
+	} else {
+		addr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
+		if err != nil {
+			return nil, err
+		}
+		destAddr = addr
+	}
+	return destAddr, nil
+}
+
+type connFactory struct {
+	hyClient.ConnFactory
+
+	NewFunc func(addr net.Addr) (net.PacketConn, error)
+}
+
+func (f *connFactory) New(addr net.Addr) (net.PacketConn, error) {
+	return f.NewFunc(addr)
+}
+
+func NewHyClient(serverAddr net.Addr, streamSettings *internet.MemoryStreamConfig) (hyClient.Client, error) {
+	tlsConfig, err := GetClientTLSConfig(streamSettings)
+	if err != nil {
+		return nil, err
+	}
+
+	config := streamSettings.ProtocolSettings.(*Config)
+	client, _, err := hyClient.NewClient(&hyClient.Config{
+		Auth:       config.GetPassword(),
+		TLSConfig:  *tlsConfig,
+		ServerAddr: serverAddr,
+		ConnFactory: &connFactory{
+			NewFunc: func(addr net.Addr) (net.PacketConn, error) {
+				rawConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
+					IP:   []byte{0, 0, 0, 0},
+					Port: 0,
+				}, streamSettings.SocketSettings)
+				if err != nil {
+					return nil, err
+				}
+				return rawConn.(*net.UDPConn), nil
+			},
+		},
+		BandwidthConfig: hyClient.BandwidthConfig{MaxTx: config.Congestion.GetUpMbps() * MBps, MaxRx: config.GetCongestion().GetDownMbps() * MBps},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return client, nil
+}
+
+func CloseHyClient(serverAddr net.Addr) error {
+	ClientMutex.Lock()
+	defer ClientMutex.Unlock()
+
+	client, found := RunningClient[serverAddr]
+	if found {
+		delete(RunningClient, serverAddr)
+		return client.Close()
+	}
+	return nil
+}
+
+func GetHyClient(serverAddr net.Addr, streamSettings *internet.MemoryStreamConfig) (hyClient.Client, error) {
+	var err error
+	var client hyClient.Client
+
+	ClientMutex.Lock()
+	client, found := RunningClient[serverAddr]
+	ClientMutex.Unlock()
+	if !found || !CheckHyClientHealthy(client) {
+		if found {
+			// retry
+			CloseHyClient(serverAddr)
+		}
+		client, err = NewHyClient(serverAddr, streamSettings)
+		if err != nil {
+			return nil, err
+		}
+		ClientMutex.Lock()
+		RunningClient[serverAddr] = client
+		ClientMutex.Unlock()
+	}
+	return client, nil
+}
+
+func CheckHyClientHealthy(client hyClient.Client) bool {
+	quicConn := client.GetQuicConn()
+	if quicConn == nil {
+		return false
+	}
+	select {
+	case <-quicConn.Context().Done():
+		return false
+	default:
+	}
+	return true
+}
+
+func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
+	config := streamSettings.ProtocolSettings.(*Config)
+
+	serverAddr, err := ResolveAddress(dest)
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := GetHyClient(serverAddr, streamSettings)
+	if err != nil {
+		CloseHyClient(serverAddr)
+		return nil, err
+	}
+
+	quicConn := client.GetQuicConn()
+	conn := &HyConn{
+		local:  quicConn.LocalAddr(),
+		remote: quicConn.RemoteAddr(),
+	}
+
+	outbound := session.OutboundFromContext(ctx)
+	network := net.Network_TCP
+	if outbound != nil {
+		network = outbound.Target.Network
+		conn.Target = outbound.Target
+	}
+
+	if network == net.Network_UDP && config.GetUseUdpExtension() { // only hysteria2 can use udpExtension
+		conn.IsUDPExtension = true
+		conn.IsServer = false
+		conn.ClientUDPSession, err = client.UDP()
+		if err != nil {
+			CloseHyClient(serverAddr)
+			return nil, err
+		}
+		return conn, nil
+	}
+
+	conn.stream, err = client.OpenStream()
+	if err != nil {
+		CloseHyClient(serverAddr)
+		return nil, err
+	}
+
+	// write TCP frame type
+	frameSize := int(quicvarint.Len(hyProtocol.FrameTypeTCPRequest))
+	buf := make([]byte, frameSize)
+	hyProtocol.VarintPut(buf, hyProtocol.FrameTypeTCPRequest)
+	conn.stream.Write(buf)
+	return conn, nil
+}
+
+func init() {
+	RunningClient = make(map[net.Addr]hyClient.Client)
+	common.Must(internet.RegisterTransportDialer(protocolName, Dial))
+}

+ 9 - 0
transport/internet/hysteria2/errors.generated.go

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

+ 128 - 0
transport/internet/hysteria2/hub.go

@@ -0,0 +1,128 @@
+package hysteria2
+
+import (
+	"context"
+
+	hyServer "github.com/apernet/hysteria/core/v2/server"
+	"github.com/apernet/quic-go"
+	"github.com/apernet/quic-go/http3"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
+)
+
+// Listener is an internet.Listener that listens for TCP connections.
+type Listener struct {
+	hyServer hyServer.Server
+	rawConn  net.PacketConn
+	addConn  internet.ConnHandler
+}
+
+// Addr implements internet.Listener.Addr.
+func (l *Listener) Addr() net.Addr {
+	return l.rawConn.LocalAddr()
+}
+
+// Close implements internet.Listener.Close.
+func (l *Listener) Close() error {
+	return l.hyServer.Close()
+}
+
+func (l *Listener) StreamHijacker(ft http3.FrameType, conn quic.Connection, stream quic.Stream, err error) (bool, error) {
+	// err always == nil
+
+	tcpConn := &HyConn{
+		stream: stream,
+		local:  conn.LocalAddr(),
+		remote: conn.RemoteAddr(),
+	}
+	l.addConn(tcpConn)
+	return true, nil
+}
+
+func (l *Listener) UdpHijacker(entry *hyServer.UdpSessionEntry, originalAddr string) {
+	addr, err := net.ResolveUDPAddr("udp", originalAddr)
+	if err != nil {
+		return
+	}
+	udpConn := &HyConn{
+		IsUDPExtension:   true,
+		IsServer:         true,
+		ServerUDPSession: entry,
+		remote:           addr,
+		local:            l.rawConn.LocalAddr(),
+	}
+	l.addConn(udpConn)
+}
+
+// Listen creates a new Listener based on configurations.
+func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
+	tlsConfig, err := GetServerTLSConfig(streamSettings)
+	if err != nil {
+		return nil, err
+	}
+
+	if address.Family().IsDomain() {
+		return nil, nil
+	}
+
+	config := streamSettings.ProtocolSettings.(*Config)
+	rawConn, err := internet.ListenSystemPacket(context.Background(),
+		&net.UDPAddr{
+			IP:   address.IP(),
+			Port: int(port),
+		}, streamSettings.SocketSettings)
+	if err != nil {
+		return nil, err
+	}
+
+	listener := &Listener{
+		rawConn: rawConn,
+		addConn: handler,
+	}
+
+	hyServer, err := hyServer.NewServer(&hyServer.Config{
+		Conn:                  rawConn,
+		TLSConfig:             *tlsConfig,
+		DisableUDP:            !config.GetUseUdpExtension(),
+		Authenticator:         &Authenticator{Password: config.GetPassword()},
+		StreamHijacker:        listener.StreamHijacker, // acceptStreams
+		BandwidthConfig:       hyServer.BandwidthConfig{MaxTx: config.Congestion.GetUpMbps() * MBps, MaxRx: config.GetCongestion().GetDownMbps() * MBps},
+		UdpSessionHijacker:    listener.UdpHijacker, // acceptUDPSession
+		IgnoreClientBandwidth: config.GetIgnoreClientBandwidth(),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	listener.hyServer = hyServer
+	go hyServer.Serve()
+	return listener, nil
+}
+
+func GetServerTLSConfig(streamSettings *internet.MemoryStreamConfig) (*hyServer.TLSConfig, error) {
+	config := tls.ConfigFromStreamSettings(streamSettings)
+	if config == nil {
+		return nil, newError(Hy2MustNeedTLS)
+	}
+	tlsConfig := config.GetTLSConfig()
+
+	return &hyServer.TLSConfig{Certificates: tlsConfig.Certificates, GetCertificate: tlsConfig.GetCertificate}, nil
+}
+
+type Authenticator struct {
+	Password string
+}
+
+func (a *Authenticator) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
+	if auth == a.Password || a.Password == "" {
+		return true, "user"
+	}
+	return false, ""
+}
+
+func init() {
+	common.Must(internet.RegisterTransportListener(protocolName, Listen))
+}

+ 155 - 0
transport/internet/hysteria2/hy2_transport_test.go

@@ -0,0 +1,155 @@
+package hysteria2_test
+
+import (
+	"context"
+	"crypto/rand"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/protocol/tls/cert"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/testing/servers/udp"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
+)
+
+func TestTCP(t *testing.T) {
+	port := udp.PickPort()
+
+	listener, err := hysteria2.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
+		ProtocolName:     "hysteria2",
+		ProtocolSettings: &hysteria2.Config{Password: "123"},
+		SecurityType:     "tls",
+		SecuritySettings: &tls.Config{
+			Certificate: []*tls.Certificate{
+				tls.ParseCertificate(
+					cert.MustGenerate(nil,
+						cert.DNSNames("www.v2fly.org"),
+					),
+				),
+			},
+		},
+	}, func(conn internet.Connection) {
+		go func() {
+			defer conn.Close()
+
+			b := buf.New()
+			defer b.Release()
+
+			for {
+				b.Clear()
+				if _, err := b.ReadFrom(conn); err != nil {
+					fmt.Println(err)
+					return
+				}
+				common.Must2(conn.Write(b.Bytes()))
+			}
+		}()
+	})
+	common.Must(err)
+
+	defer listener.Close()
+
+	time.Sleep(time.Second)
+
+	dctx := context.Background()
+	conn, err := hysteria2.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
+		ProtocolName:     "hysteria2",
+		ProtocolSettings: &hysteria2.Config{Password: "123"},
+		SecurityType:     "tls",
+		SecuritySettings: &tls.Config{
+			ServerName:    "www.v2fly.org",
+			AllowInsecure: true,
+		},
+	})
+	common.Must(err)
+	defer conn.Close()
+
+	const N = 1000
+	b1 := make([]byte, N)
+	common.Must2(rand.Read(b1))
+	b2 := buf.New()
+
+	common.Must2(conn.Write(b1))
+
+	b2.Clear()
+	common.Must2(b2.ReadFullFrom(conn, N))
+	if r := cmp.Diff(b2.Bytes(), b1); r != "" {
+		t.Error(r)
+	}
+}
+
+func TestUDP(t *testing.T) {
+	port := udp.PickPort()
+
+	listener, err := hysteria2.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
+		ProtocolName:     "hysteria2",
+		ProtocolSettings: &hysteria2.Config{Password: "123", UseUdpExtension: true},
+		SecurityType:     "tls",
+		SecuritySettings: &tls.Config{
+			Certificate: []*tls.Certificate{
+				tls.ParseCertificate(
+					cert.MustGenerate(nil,
+						cert.DNSNames("www.v2fly.org"),
+					),
+				),
+			},
+		},
+	}, func(conn internet.Connection) {
+		fmt.Println("incoming")
+		go func() {
+			defer conn.Close()
+
+			b := buf.New()
+			defer b.Release()
+
+			for {
+				b.Clear()
+				if _, err := b.ReadFrom(conn); err != nil {
+					fmt.Println(err)
+					return
+				}
+				common.Must2(conn.Write(b.Bytes()))
+			}
+		}()
+	})
+	common.Must(err)
+
+	defer listener.Close()
+
+	time.Sleep(time.Second)
+
+	address, err := net.ParseDestination("udp:127.0.0.1:1180")
+	dctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: address})
+
+	conn, err := hysteria2.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
+		ProtocolName:     "hysteria2",
+		ProtocolSettings: &hysteria2.Config{Password: "123", UseUdpExtension: true},
+		SecurityType:     "tls",
+		SecuritySettings: &tls.Config{
+			ServerName:    "www.v2fly.org",
+			AllowInsecure: true,
+		},
+	})
+	common.Must(err)
+	defer conn.Close()
+
+	const N = 1000
+	b1 := make([]byte, N)
+	common.Must2(rand.Read(b1))
+	common.Must2(conn.Write(b1))
+
+	b2 := buf.New()
+	b2.Clear()
+	common.Must2(b2.ReadFullFrom(conn, N))
+	if r := cmp.Diff(b2.Bytes(), b1); r != "" {
+		t.Error(r)
+	}
+}

+ 18 - 0
transport/internet/hysteria2/hysteria2.go

@@ -0,0 +1,18 @@
+package hysteria2
+
+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 = "hysteria2"
+)
+
+func init() {
+	common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
+		return new(Config)
+	}))
+}