diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 8381f19555..07174bdb1b 100644 --- a/.github/workflows/linter.yml +++ b/.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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 827263a929..3176a95a7b 100644 --- a/.github/workflows/test.yml +++ b/.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 diff --git a/.gitignore b/.gitignore index e034f463d5..7b08419960 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ vprotogen errorgen !common/errors/errorgen/ *.dat +*~ +[._]*.un~ diff --git a/README.md b/README.md index ecbbd9e940..714f030129 100644 --- a/README.md +++ b/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) diff --git a/app/dns/config.pb.go b/app/dns/config.pb.go index 79754e0b2f..a2b1580a6a 100644 --- a/app/dns/config.pb.go +++ b/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 ( diff --git a/app/router/command/command_test.go b/app/router/command/command_test.go index db2a645285..dac3b69b0f 100644 --- a/app/router/command/command_test.go +++ b/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 } diff --git a/app/router/config.pb.go b/app/router/config.pb.go index e0ea6758c2..d3f79a83b9 100644 --- a/app/router/config.pb.go +++ b/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 diff --git a/config.pb.go b/config.pb.go index ba90ada165..dd80bf4d55 100644 --- a/config.pb.go +++ b/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 diff --git a/go.mod b/go.mod index 514c598708..07ddf2ee50 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ toolchain go1.21.4 require ( github.com/adrg/xdg v0.4.0 + github.com/apernet/hysteria/core/v2 v2.4.5 + github.com/apernet/quic-go v0.44.1-0.20240520215222-bb2e53664023 github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/render v1.0.3 github.com/go-playground/validator/v10 v10.20.0 @@ -32,12 +34,12 @@ require ( github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 go.starlark.net v0.0.0-20230612165344-9532f5667272 go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 - golang.org/x/crypto v0.22.0 - golang.org/x/net v0.24.0 + golang.org/x/crypto v0.23.0 + golang.org/x/net v0.25.0 golang.org/x/sync v0.7.0 - golang.org/x/sys v0.19.0 + golang.org/x/sys v0.20.0 google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.34.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h12.io/socks v1.0.3 @@ -59,29 +61,35 @@ 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/dtls/v2 v2.2.7 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/sctp v1.8.7 // indirect 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-20230725093048-515e97ebf090 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.19.0 // indirect + golang.org/x/tools v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect ) 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-20240621174944-4c4ba8b7f163 + +//replace github.com/apernet/hysteria/core/v2 => ../hysteria/core/ diff --git a/go.sum b/go.sum index 262e14c6db..2ec6514b4d 100644 --- a/go.sum +++ b/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-20240621174944-4c4ba8b7f163 h1:yc0h7bAT4P6yfF7yO5GQGaWFF8ZJbctzUT7NtkY1vPE= +github.com/JimmyHuang454/hysteria/core/v2 v2.0.0-20240621174944-4c4ba8b7f163/go.mod h1:RskXoU8aQ3EcpwxWK8tK0kWNNJveve7RG23Us+1GoHI= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= @@ -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.44.1-0.20240520215222-bb2e53664023 h1:UTrvVPt+GfeOeli9/3gvpCDz2Jd5UEn3YotfP0u/pok= +github.com/apernet/quic-go v0.44.1-0.20240520215222-bb2e53664023/go.mod h1:UkcG7+34BM+bbH2RFVKtHQp3mR7h8yJHx4z95lZ7sx4= 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= @@ -130,8 +134,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= @@ -186,8 +190,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= @@ -220,10 +225,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= @@ -265,6 +270,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.43.0 h1:sjtsTKWX0dsHpuMJvLxGqoQdtgJnbAPWY+W+5vjYW/g= github.com/quic-go/quic-go v0.43.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/refraction-networking/utls v1.6.5 h1:Jlfqgs/t1Uy6FHHQ8Fz9ZTrRmP/zS7d/NZw7BLahaL8= @@ -273,6 +280,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= @@ -301,6 +310,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= @@ -345,6 +356,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= @@ -364,15 +377,15 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= -golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -392,8 +405,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -418,8 +431,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -467,8 +480,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -486,8 +499,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -516,8 +529,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -558,13 +571,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= diff --git a/infra/conf/v4/hysteria2.go b/infra/conf/v4/hysteria2.go new file mode 100644 index 0000000000..a5e08ed831 --- /dev/null +++ b/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 +} diff --git a/infra/conf/v4/transport_internet.go b/infra/conf/v4/transport_internet.go index 1a18a45d73..9584205dee 100644 --- a/infra/conf/v4/transport_internet.go +++ b/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 { diff --git a/infra/conf/v4/v2ray.go b/infra/conf/v4/v2ray.go index 399fdc22a1..10cbf87b72 100644 --- a/infra/conf/v4/v2ray.go +++ b/infra/conf/v4/v2ray.go @@ -32,6 +32,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{ @@ -43,6 +44,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") diff --git a/infra/vprotogen/main.go b/infra/vprotogen/main.go index ee367e2ab8..05ce25b7fa 100644 --- a/infra/vprotogen/main.go +++ b/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 { diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 200f122830..80f13f72b9 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -54,6 +54,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 @@ -78,6 +79,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" diff --git a/proxy/hysteria2/client.go b/proxy/hysteria2/client.go new file mode 100644 index 0000000000..b47b090ddc --- /dev/null +++ b/proxy/hysteria2/client.go @@ -0,0 +1,169 @@ +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)) + })) +} diff --git a/proxy/hysteria2/config.go b/proxy/hysteria2/config.go new file mode 100644 index 0000000000..924e992529 --- /dev/null +++ b/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 +} diff --git a/proxy/hysteria2/config.pb.go b/proxy/hysteria2/config.pb.go new file mode 100644 index 0000000000..e7c913a1d8 --- /dev/null +++ b/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 +} diff --git a/proxy/hysteria2/config.proto b/proxy/hysteria2/config.proto new file mode 100644 index 0000000000..822d414731 --- /dev/null +++ b/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; +} diff --git a/proxy/hysteria2/errors.generated.go b/proxy/hysteria2/errors.generated.go new file mode 100644 index 0000000000..1053170313 --- /dev/null +++ b/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{}) +} diff --git a/proxy/hysteria2/hysteria2.go b/proxy/hysteria2/hysteria2.go new file mode 100644 index 0000000000..013f7d5ccd --- /dev/null +++ b/proxy/hysteria2/hysteria2.go @@ -0,0 +1 @@ +package hysteria2 diff --git a/proxy/hysteria2/protocol.go b/proxy/hysteria2/protocol.go new file mode 100644 index 0000000000..49b76d0f2a --- /dev/null +++ b/proxy/hysteria2/protocol.go @@ -0,0 +1,198 @@ +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 { + padding := "Jimmy Was Here" + paddingLen := len(padding) + addressAndPort := c.Target.Address.String() + ":" + c.Target.Port.String() + addressLen := len(addressAndPort) + size := QuicLen(addressLen) + addressLen + QuicLen(paddingLen) + paddingLen + + 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) + if err == nil { + c.TCPHeaderSent = true + } + 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 +} diff --git a/proxy/hysteria2/server.go b/proxy/hysteria2/server.go new file mode 100644 index 0000000000..3bb48676c0 --- /dev/null +++ b/proxy/hysteria2/server.go @@ -0,0 +1,229 @@ +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 + validator *Validator + packetEncoding packetaddr.PacketAddrType +} + +// NewServer creates a new inbound handler. +func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { + validator := new(Validator) + for _, user := range config.Users { + u, err := user.ToMemoryUser() + if err != nil { + return nil, newError("failed to get user").Base(err).AtError() + } + + if err := validator.Add(u); err != nil { + return nil, newError("failed to add user").Base(err).AtError() + } + } + + v := core.MustFromContext(ctx) + server := &Server{ + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + validator: validator, + } + 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) + } + } + } +} // }}} diff --git a/proxy/hysteria2/validator.go b/proxy/hysteria2/validator.go new file mode 100644 index 0000000000..cd7475a26f --- /dev/null +++ b/proxy/hysteria2/validator.go @@ -0,0 +1,51 @@ +package hysteria2 + +import ( + "strings" + "sync" + + "github.com/v2fly/v2ray-core/v5/common/protocol" +) + +// Validator stores valid trojan users. +type Validator struct { + // Considering email's usage here, map + sync.Mutex/RWMutex may have better performance. + email sync.Map + users sync.Map +} + +// Add a trojan user, Email must be empty or unique. +func (v *Validator) Add(u *protocol.MemoryUser) error { + if u.Email != "" { + _, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u) + if loaded { + return newError("User ", u.Email, " already exists.") + } + } + v.users.Store(hexString(u.Account.(*MemoryAccount).Key), u) + return nil +} + +// Del a trojan user with a non-empty Email. +func (v *Validator) Del(e string) error { + if e == "" { + return newError("Email must not be empty.") + } + le := strings.ToLower(e) + u, _ := v.email.Load(le) + if u == nil { + return newError("User ", e, " not found.") + } + v.email.Delete(le) + v.users.Delete(hexString(u.(*protocol.MemoryUser).Account.(*MemoryAccount).Key)) + return nil +} + +// Get a trojan user with hashed key, nil if user doesn't exist. +func (v *Validator) Get(hash string) *protocol.MemoryUser { + u, _ := v.users.Load(hash) + if u != nil { + return u.(*protocol.MemoryUser) + } + return nil +} diff --git a/release/config/hy2/hysteria2_offical_v4.json b/release/config/hy2/hysteria2_offical_v4.json new file mode 100644 index 0000000000..2046c31d17 --- /dev/null +++ b/release/config/hy2/hysteria2_offical_v4.json @@ -0,0 +1,59 @@ +{ + "log": { + "loglevel": "debug" + }, + "inbounds": [{ + "port": 1080, + "listen": "127.0.0.1", + "tag": "http", + "protocol": "http" + }, { + "port": 1443, + "listen": "127.0.0.1", + "tag": "hy2_in", + "protocol": "hysteria2", + "streamSettings": { + "network": "hysteria2", + "security": "tls", + "hy2Settings": { + "password": "your-password" + } + } + }], + "outbounds": [{ + "protocol": "freedom", + "tag": "direct" + }, { + "tag": "hy2_out", + "protocol": "hysteria2", + "settings": { + "servers": [{ + "address": "127.0.0.1", + "port": 1443 + }] + }, + "streamSettings": { + "network": "hysteria2", + "security": "tls", + "tlsSettings": { + "allowInsecure": true + }, + "hy2Settings": { + "password": "your-password" + } + } + }], + "routing": { + "rules": [{ + "type": "field", + "outboundTag": "direct", + "inboundTag": ["hy2_in"] + }, + { + "type": "field", + "outboundTag": "hy2_out", + "inboundTag": ["http"] + } + ] + } +} diff --git a/release/config/hy2/hysteria2_official.json5 b/release/config/hy2/hysteria2_official.json5 new file mode 100644 index 0000000000..d1db1dfe51 --- /dev/null +++ b/release/config/hy2/hysteria2_official.json5 @@ -0,0 +1,83 @@ +{ + "log": { + "access":{"level": "Debug"} + }, + "inbounds": [{ + "port": 1080, + "listen": "127.0.0.1", + "tag": "http", + "protocol": "http" + }, { + "port": 1443, + "listen": "127.0.0.1", + "tag": "hy2_in_bbr", + "protocol": "hysteria2", + "streamSettings": { + "transport": "hysteria2", + "transportSettings": { + "congestion": { + "type": "bbr" + }, + "password": "your-password" + "use_udp_extension": true + }, + "security": "tls" + } + },{ + "port": 1281, + "listen": "127.0.0.1", + "tag": "hy2_in_brutal", + "protocol": "hysteria2", + "streamSettings": { + "transport": "hysteria2", + "transportSettings": { + "congestion": { + "type": "brutal", + "up_mbps":20, + "down_mbps": 100 + }, + "use_udp_extension": true + }, + "security": "tls" + } + }], + "outbounds": [{ + "protocol": "freedom", + "tag": "direct" + }, { + "tag": "hy2_out", + "protocol": "hysteria2", + "settings": { + "server": [{ + "address": "127.0.0.1", + "port": 1443 + }] + }, + "streamSettings": { + "transport": "hysteria2", + "transportSettings": { + "congestion": { + "type": "bbr" + }, + "password": "your-password" + }, + "security": "tls", + "securitySettings": { + "allowInsecure": true + } + } + }], + "routing": { + "rules": [{ + "type": "field", + "outboundTag": "direct", + "inboundTag": ["hy2_in_bbr", "hy2_in_brutal"] + }, + { + "type": "field", + "outboundTag": "hy2_out", + "inboundTag": ["http"] + } + ] + } +} diff --git a/release/config/hy2/hysteria2_vmess.json5 b/release/config/hy2/hysteria2_vmess.json5 new file mode 100644 index 0000000000..386ae616b7 --- /dev/null +++ b/release/config/hy2/hysteria2_vmess.json5 @@ -0,0 +1,83 @@ +{ + "log": { + "access":{"level": "Debug"} + }, + "inbounds": [{ + "port": 1080, + "listen": "127.0.0.1", + "tag": "http", + "protocol": "http" + }, { + "port": 1280, + "listen": "127.0.0.1", + "tag": "hy2_in_bbr", + "protocol": "vmess", + "settings": { + "users": ["23ad6b10-8d1a-40f7-8ad0-e3e35cd38297"] + }, + "streamSettings": { + "transport": "hysteria2", + "transportSettings": { + "congestion": { + "type": "bbr" + }, + "use_udp_extension": true + }, + "security": "tls" + } + },{ + "port": 1281, + "listen": "127.0.0.1", + "tag": "hy2_in_brutal", + "protocol": "vmess", + "settings": { + "users": ["23ad6b10-8d1a-40f7-8ad0-e3e35cd38297"] + }, + "streamSettings": { + "transport": "hysteria2", + "transportSettings": { + "congestion": { + "type": "brutal", + "up_mbps":20, + "down_mbps": 100 + }, + "use_udp_extension": true + }, + "security": "tls" + } + }], + "outbounds": [{ + "protocol": "freedom", + "tag": "direct" + }, { + "tag": "hy2_out", + "protocol": "vmess", + "settings": { + "address": "127.0.0.1", + "port": 1280, + "uuid": "23ad6b10-8d1a-40f7-8ad0-e3e35cd38297" + }, + "streamSettings": { + "transport": "hysteria2", + "transportSettings": { + "congestion": { + "type": "bbr" + } + }, + "security": "tls" + } + }], + "routing": { + "rules": [{ + "type": "field", + "outboundTag": "direct", + "inboundTag": ["hy2_in_bbr", "hy2_in_brutal"] + }, + { + "type": "field", + "outboundTag": "hy2_out", + "inboundTag": ["http"] + } + ] + } +} diff --git a/release/config/hy2/hysteria2_vmess_v4.json b/release/config/hy2/hysteria2_vmess_v4.json new file mode 100644 index 0000000000..47d373c642 --- /dev/null +++ b/release/config/hy2/hysteria2_vmess_v4.json @@ -0,0 +1,60 @@ +{ + "log": { + "loglevel": "debug" + }, + "inbounds": [{ + "port": 1080, + "listen": "127.0.0.1", + "tag": "http", + "protocol": "http" + }, { + "port": 1280, + "listen": "127.0.0.1", + "tag": "hy2_in", + "protocol": "vmess", + "settings": { + "clients": [{ + "id": "23ad6b10-8d1a-40f7-8ad0-e3e35cd38297", + "alterId": 0 + }] + }, + "streamSettings": { + "network": "hysteria2", + "security": "tls" + } + }], + "outbounds": [{ + "protocol": "freedom", + "tag": "direct" + }, { + "tag": "hy2_out", + "protocol": "vmess", + "settings": { + "vnext": [{ + "address": "127.0.0.1", + "port": 1280, + "users": [{ + "id": "23ad6b10-8d1a-40f7-8ad0-e3e35cd38297", + "security": "auto" + }] + }] + }, + "streamSettings": { + "network": "hysteria2", + "security": "tls" + } + }], + "routing": { + "rules": [{ + "type": "field", + "outboundTag": "direct", + "inboundTag": ["hy2_in"] + }, + { + "type": "field", + "outboundTag": "hy2_out", + "inboundTag": ["http"] + } + ] + } +} diff --git a/testing/scenarios/hy2_test.go b/testing/scenarios/hy2_test.go new file mode 100644 index 0000000000..39c1d0688a --- /dev/null +++ b/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) + } +} diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index a07b206bc3..d4ef6c1c10 100644 --- a/transport/internet/grpc/dial.go +++ b/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 { diff --git a/transport/internet/hysteria2/config.pb.go b/transport/internet/hysteria2/config.pb.go new file mode 100644 index 0000000000..61d3da1879 --- /dev/null +++ b/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 +} diff --git a/transport/internet/hysteria2/config.proto b/transport/internet/hysteria2/config.proto new file mode 100644 index 0000000000..ac5f278445 --- /dev/null +++ b/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; +} diff --git a/transport/internet/hysteria2/conn.go b/transport/internet/hysteria2/conn.go new file mode 100644 index 0000000000..351ff7001c --- /dev/null +++ b/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) +} diff --git a/transport/internet/hysteria2/dialer.go b/transport/internet/hysteria2/dialer.go new file mode 100644 index 0000000000..d5b0648302 --- /dev/null +++ b/transport/internet/hysteria2/dialer.go @@ -0,0 +1,198 @@ +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 + +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{ + TLSConfig: *tlsConfig, + Auth: config.GetPassword(), + 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 + }, + }, + }) + 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 || !CheckHyClentHealthy(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 CheckHyClentHealthy(client hyClient.Client) bool { + // TODO: Clean idle connections + 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)) +} diff --git a/transport/internet/hysteria2/errors.generated.go b/transport/internet/hysteria2/errors.generated.go new file mode 100644 index 0000000000..1053170313 --- /dev/null +++ b/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{}) +} diff --git a/transport/internet/hysteria2/hub.go b/transport/internet/hysteria2/hub.go new file mode 100644 index 0000000000..f493d6efdd --- /dev/null +++ b/transport/internet/hysteria2/hub.go @@ -0,0 +1,127 @@ +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, + Authenticator: &Authenticator{Password: config.GetPassword()}, + IgnoreClientBandwidth: config.GetIgnoreClientBandwidth(), + DisableUDP: !config.GetUseUdpExtension(), + StreamHijacker: listener.StreamHijacker, // acceptStreams + UdpSessionHijacker: listener.UdpHijacker, // acceptUDPSession + }) + 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)) +} diff --git a/transport/internet/hysteria2/hy2_transport_test.go b/transport/internet/hysteria2/hy2_transport_test.go new file mode 100644 index 0000000000..e4a5c1fde8 --- /dev/null +++ b/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) + } +} diff --git a/transport/internet/hysteria2/hysteria2.go b/transport/internet/hysteria2/hysteria2.go new file mode 100644 index 0000000000..3280d6a5dc --- /dev/null +++ b/transport/internet/hysteria2/hysteria2.go @@ -0,0 +1,19 @@ +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" + internalDomain = "hysteria2.internal.v2fly.org" +) + +func init() { + common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { + return new(Config) + })) +}