diff --git a/app/observatory/burst/config.pb.go b/app/observatory/burst/config.pb.go index ffbd0689..df0d7780 100644 --- a/app/observatory/burst/config.pb.go +++ b/app/observatory/burst/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.35.1 +// protoc-gen-go v1.35.2 // protoc v5.28.2 // source: app/observatory/burst/config.proto @@ -89,7 +89,8 @@ type HealthPingConfig struct { // sampling count is the amount of recent ping results which are kept for calculation SamplingCount int32 `protobuf:"varint,4,opt,name=samplingCount,proto3" json:"samplingCount,omitempty"` // ping timeout, int64 values of time.Duration - Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` + Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` + ExpectedResponseCode []int32 `protobuf:"varint,6,rep,packed,name=expectedResponseCode,proto3" json:"expectedResponseCode,omitempty"` } func (x *HealthPingConfig) Reset() { @@ -157,6 +158,13 @@ func (x *HealthPingConfig) GetTimeout() int64 { return 0 } +func (x *HealthPingConfig) GetExpectedResponseCode() []int32 { + if x != nil { + return x.ExpectedResponseCode + } + return nil +} + var File_app_observatory_burst_config_proto protoreflect.FileDescriptor var file_app_observatory_burst_config_proto_rawDesc = []byte{ @@ -173,7 +181,7 @@ var file_app_observatory_burst_config_proto_rawDesc = []byte{ 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, - 0xb4, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0xe8, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, @@ -184,14 +192,18 @@ var file_app_observatory_burst_config_proto_rawDesc = []byte{ 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, - 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, - 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, - 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x62, 0x75, 0x72, 0x73, 0x74, 0xaa, 0x02, 0x1a, 0x58, 0x72, - 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, - 0x72, 0x79, 0x2e, 0x42, 0x75, 0x72, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x05, 0x52, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, + 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x50, 0x01, 0x5a, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, + 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x62, 0x75, 0x72, 0x73, 0x74, 0xaa, + 0x02, 0x1a, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x42, 0x75, 0x72, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/observatory/burst/config.proto b/app/observatory/burst/config.proto index ead75478..083385d7 100644 --- a/app/observatory/burst/config.proto +++ b/app/observatory/burst/config.proto @@ -26,4 +26,5 @@ message HealthPingConfig { int32 samplingCount = 4; // ping timeout, int64 values of time.Duration int64 timeout = 5; + repeated int32 expectedResponseCode = 6; } diff --git a/app/observatory/burst/healthping.go b/app/observatory/burst/healthping.go index cd4d5fc0..dfddb712 100644 --- a/app/observatory/burst/healthping.go +++ b/app/observatory/burst/healthping.go @@ -13,11 +13,12 @@ import ( // HealthPingSettings holds settings for health Checker type HealthPingSettings struct { - Destination string `json:"destination"` - Connectivity string `json:"connectivity"` - Interval time.Duration `json:"interval"` - SamplingCount int `json:"sampling"` - Timeout time.Duration `json:"timeout"` + Destination string `json:"destination"` + Connectivity string `json:"connectivity"` + Interval time.Duration `json:"interval"` + SamplingCount int `json:"sampling"` + Timeout time.Duration `json:"timeout"` + ExpectedResponseCode []int32 `json:"expectedResponseCode"` } // HealthPing is the health checker for balancers @@ -36,11 +37,12 @@ func NewHealthPing(ctx context.Context, config *HealthPingConfig) *HealthPing { settings := &HealthPingSettings{} if config != nil { settings = &HealthPingSettings{ - Connectivity: strings.TrimSpace(config.Connectivity), - Destination: strings.TrimSpace(config.Destination), - Interval: time.Duration(config.Interval), - SamplingCount: int(config.SamplingCount), - Timeout: time.Duration(config.Timeout), + Connectivity: strings.TrimSpace(config.Connectivity), + Destination: strings.TrimSpace(config.Destination), + Interval: time.Duration(config.Interval), + SamplingCount: int(config.SamplingCount), + Timeout: time.Duration(config.Timeout), + ExpectedResponseCode: config.ExpectedResponseCode, } } if settings.Destination == "" { @@ -48,6 +50,7 @@ func NewHealthPing(ctx context.Context, config *HealthPingConfig) *HealthPing { // https://github.com/chromium/chromium/blob/main/components/safety_check/url_constants.cc#L10 // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/safety_check/url_constants.cc#10 settings.Destination = "https://connectivitycheck.gstatic.com/generate_204" + settings.ExpectedResponseCode = []int32{ 204 } } if settings.Interval == 0 { settings.Interval = time.Duration(1) * time.Minute @@ -152,6 +155,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) h.Settings.Destination, h.Settings.Timeout, handler, + h.Settings.ExpectedResponseCode, ) for i := 0; i < rounds; i++ { delay := time.Duration(0) diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go index de1465b9..a3d8c5ad 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -5,26 +5,30 @@ import ( "net/http" "time" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/transport/internet/tagged" ) type pingClient struct { - destination string - httpClient *http.Client + destination string + httpClient *http.Client + expectedResponseCode []int32 } -func newPingClient(ctx context.Context, destination string, timeout time.Duration, handler string) *pingClient { +func newPingClient(ctx context.Context, destination string, timeout time.Duration, handler string, expectedResponseCode []int32) *pingClient { return &pingClient{ - destination: destination, - httpClient: newHTTPClient(ctx, handler, timeout), + destination: destination, + httpClient: newHTTPClient(ctx, handler, timeout), + expectedResponseCode: expectedResponseCode, } } func newDirectPingClient(destination string, timeout time.Duration) *pingClient { return &pingClient{ - destination: destination, - httpClient: &http.Client{Timeout: timeout}, + destination: destination, + httpClient: &http.Client{Timeout: timeout}, + expectedResponseCode: []int32{}, } } @@ -63,6 +67,18 @@ func (s *pingClient) MeasureDelay() (time.Duration, error) { if err != nil { return rttFailed, err } + if len(s.expectedResponseCode) > 0 { + found := false + for _, c := range s.expectedResponseCode { + if c == int32(resp.StatusCode) { + found = true + break + } + } + if !found { + return rttFailed, errors.New("Unexpected response code: ", resp.StatusCode, " expected: ", s.expectedResponseCode) + } + } // don't wait for body resp.Body.Close() return time.Since(start), nil diff --git a/infra/conf/router_strategy.go b/infra/conf/router_strategy.go index 98bcc8d1..5c01f6cc 100644 --- a/infra/conf/router_strategy.go +++ b/infra/conf/router_strategy.go @@ -46,20 +46,22 @@ type strategyLeastLoadConfig struct { // healthCheckSettings holds settings for health Checker type healthCheckSettings struct { - Destination string `json:"destination"` - Connectivity string `json:"connectivity"` - Interval duration.Duration `json:"interval"` - SamplingCount int `json:"sampling"` - Timeout duration.Duration `json:"timeout"` + Destination string `json:"destination"` + Connectivity string `json:"connectivity"` + Interval duration.Duration `json:"interval"` + SamplingCount int `json:"sampling"` + Timeout duration.Duration `json:"timeout"` + ExpectedResponseCode []int32 `json:"expectedResponseCode"` } func (h healthCheckSettings) Build() (proto.Message, error) { return &burst.HealthPingConfig{ - Destination: h.Destination, - Connectivity: h.Connectivity, - Interval: int64(h.Interval), - Timeout: int64(h.Timeout), - SamplingCount: int32(h.SamplingCount), + Destination: h.Destination, + Connectivity: h.Connectivity, + Interval: int64(h.Interval), + Timeout: int64(h.Timeout), + SamplingCount: int32(h.SamplingCount), + ExpectedResponseCode: h.ExpectedResponseCode, }, nil }