diff --git a/infra/conf/duration.go b/infra/conf/duration.go new file mode 100644 index 000000000..a033751ea --- /dev/null +++ b/infra/conf/duration.go @@ -0,0 +1,33 @@ +package conf + +import ( + "encoding/json" + "fmt" + "time" +) + +type Duration int64 + +func (d *Duration) MarshalJSON() ([]byte, error) { + dr := time.Duration(*d) + return json.Marshal(dr.String()) +} + +func (d *Duration) UnmarshalJSON(b []byte) error { + var v interface{} + if err := json.Unmarshal(b, &v); err != nil { + return err + } + switch value := v.(type) { + case string: + var err error + dr, err := time.ParseDuration(value) + if err != nil { + return err + } + *d = Duration(dr) + return nil + default: + return fmt.Errorf("invalid duration: %v", v) + } +} diff --git a/infra/conf/duration_test.go b/infra/conf/duration_test.go new file mode 100644 index 000000000..95add1d23 --- /dev/null +++ b/infra/conf/duration_test.go @@ -0,0 +1,33 @@ +package conf_test + +import ( + "encoding/json" + "testing" + "time" + + "github.com/v2fly/v2ray-core/v4/infra/conf" +) + +type testWithDuration struct { + Duration conf.Duration +} + +func TestDurationJSON(t *testing.T) { + expected := &testWithDuration{ + Duration: conf.Duration(time.Hour), + } + data, err := json.Marshal(expected) + if err != nil { + t.Error(err) + return + } + actual := &testWithDuration{} + err = json.Unmarshal(data, &actual) + if err != nil { + t.Error(err) + return + } + if actual.Duration != expected.Duration { + t.Errorf("expected: %s, actual: %s", time.Duration(expected.Duration), time.Duration(actual.Duration)) + } +} diff --git a/infra/conf/router_strategy.go b/infra/conf/router_strategy.go index 597d387c2..05b1d67f4 100644 --- a/infra/conf/router_strategy.go +++ b/infra/conf/router_strategy.go @@ -1,8 +1,6 @@ package conf import ( - "time" - "github.com/golang/protobuf/proto" "github.com/v2fly/v2ray-core/v4/app/router" @@ -29,22 +27,29 @@ func (v *strategyEmptyConfig) Build() (proto.Message, error) { } type strategyLeastLoadConfig struct { - // note the time values of the HealthCheck holds is not - // 'time.Duration' but plain number, sice they were parsed - // directly from json - HealthCheck *router.HealthPingSettings `json:"healthCheck,omitempty"` + // health check settings + HealthCheck *healthCheckSettings `json:"healthCheck,omitempty"` // weight settings Costs []*router.StrategyWeight `json:"costs,omitempty"` - // ping rtt baselines (ms) - Baselines []int `json:"baselines,omitempty"` + // ping rtt baselines + Baselines []Duration `json:"baselines,omitempty"` // expected nodes count to select Expected int32 `json:"expected,omitempty"` - // max acceptable rtt (ms), filter away high delay nodes. defalut 0 - MaxRTT int `json:"maxRTT,omitempty"` + // max acceptable rtt, filter away high delay nodes. defalut 0 + MaxRTT Duration `json:"maxRTT,omitempty"` // acceptable failure rate Tolerance float64 `json:"tolerance,omitempty"` } +// healthCheckSettings holds settings for health Checker +type healthCheckSettings struct { + Destination string `json:"destination"` + Connectivity string `json:"connectivity"` + Interval Duration `json:"interval"` + SamplingCount int `json:"sampling"` + Timeout Duration `json:"timeout"` +} + // Build implements Buildable. func (v *strategyLeastLoadConfig) Build() (proto.Message, error) { config := &router.StrategyLeastLoadConfig{ @@ -54,8 +59,8 @@ func (v *strategyLeastLoadConfig) Build() (proto.Message, error) { config.HealthCheck = &router.HealthPingConfig{ Destination: v.HealthCheck.Destination, Connectivity: v.HealthCheck.Connectivity, - Interval: int64(v.HealthCheck.Interval * time.Second), - Timeout: int64(v.HealthCheck.Timeout * time.Second), + Interval: int64(v.HealthCheck.Interval), + Timeout: int64(v.HealthCheck.Timeout), SamplingCount: int32(v.HealthCheck.SamplingCount), } } @@ -71,7 +76,7 @@ func (v *strategyLeastLoadConfig) Build() (proto.Message, error) { if config.Expected < 0 { config.Expected = 0 } - config.MaxRTT = int64(time.Duration(v.MaxRTT) * time.Millisecond) + config.MaxRTT = int64(v.MaxRTT) if config.MaxRTT < 0 { config.MaxRTT = 0 } @@ -80,7 +85,7 @@ func (v *strategyLeastLoadConfig) Build() (proto.Message, error) { if b <= 0 { continue } - config.Baselines = append(config.Baselines, int64(time.Duration(b)*time.Millisecond)) + config.Baselines = append(config.Baselines, int64(b)) } return config, nil } diff --git a/infra/conf/router_test.go b/infra/conf/router_test.go index 32e7b3e45..4e11cf712 100644 --- a/infra/conf/router_test.go +++ b/infra/conf/router_test.go @@ -78,9 +78,9 @@ func TestRouterConfig(t *testing.T) { "type": "leastload", "settings": { "healthCheck": { - "interval": 300, + "interval": "5m0s", "sampling": 2, - "timeout": 3, + "timeout": "5s", "destination": "dest", "connectivity": "conn" }, @@ -91,9 +91,9 @@ func TestRouterConfig(t *testing.T) { "value": 5 } ], - "baselines": [400, 600], + "baselines": ["400ms", "600ms"], "expected": 6, - "maxRTT": 1000, + "maxRTT": "1000ms", "tolerance": 0.5 } }, @@ -116,9 +116,9 @@ func TestRouterConfig(t *testing.T) { Strategy: "leastload", StrategySettings: serial.ToTypedMessage(&router.StrategyLeastLoadConfig{ HealthCheck: &router.HealthPingConfig{ - Interval: int64(time.Duration(300) * time.Second), + Interval: int64(time.Duration(5) * time.Minute), SamplingCount: 2, - Timeout: int64(time.Duration(3) * time.Second), + Timeout: int64(time.Duration(5) * time.Second), Destination: "dest", Connectivity: "conn", },