diff --git a/cmd/chihaya/main.go b/cmd/chihaya/main.go index 69277bd..4796d54 100644 --- a/cmd/chihaya/main.go +++ b/cmd/chihaya/main.go @@ -23,6 +23,7 @@ import ( // Middleware _ "github.com/chihaya/chihaya/middleware/deniability" + _ "github.com/chihaya/chihaya/middleware/varinterval" _ "github.com/chihaya/chihaya/server/store/middleware/client" _ "github.com/chihaya/chihaya/server/store/middleware/infohash" _ "github.com/chihaya/chihaya/server/store/middleware/ip" diff --git a/middleware/varinterval/README.md b/middleware/varinterval/README.md new file mode 100644 index 0000000..2f29161 --- /dev/null +++ b/middleware/varinterval/README.md @@ -0,0 +1,34 @@ +## Announce Interval Variation Middleware + +This package provides the announce middleware `varinterval` which randomizes the announce interval. + +### Functionality + +This middleware will choose random announces and modify the `interval` and `min_interval` fields. +A random number of seconds will be added to the `interval` field and, if desired, also to the `min_interval` field. + +Note that if a response is picked for modification and `min_interval` should be changed as well, both `interval` and `min_interval` will be modified by the same amount. + +### Use Case + +Use this middleware to avoid recurring load spikes on the tracker. +By randomizing the announce interval, load spikes will flatten out after a few cycles. + +### Configuration + +This middleware provides the following parameters for configuration: + +- `modify_response_probability` (float, >0, <= 1) indicates the probability by which a response will be augmented with random peers. +- `max_increase_delta` (int, >0) sets an upper boundary (inclusive) for the amount of seconds added. +- `modify_min_interval` (boolean) whether to modify the `min_interval` field as well. + +An example config might look like this: + + chihaya: + tracker: + announce_middleware: + - name: varinterval + config: + modify_response_probability: 0.2 + max_increase_delta: 60 + modify_min_interval: true diff --git a/middleware/varinterval/config.go b/middleware/varinterval/config.go new file mode 100644 index 0000000..19eeaf8 --- /dev/null +++ b/middleware/varinterval/config.go @@ -0,0 +1,43 @@ +// Copyright 2016 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +package varinterval + +import ( + "gopkg.in/yaml.v2" + + "github.com/chihaya/chihaya" +) + +// Config represents the configuration for the varinterval middleware. +type Config struct { + // ModifyResponseProbability is the probability by which a response will + // be modified. + ModifyResponseProbability float32 `yaml:"modify_response_probability"` + + // MaxIncreaseDelta is the amount of seconds that will be added at most. + MaxIncreaseDelta int `yaml:"max_increase_delta"` + + // ModifyMinInterval specifies whether min_interval should be increased + // as well. + ModifyMinInterval bool `yaml:"modify_min_interval"` +} + +// newConfig parses the given MiddlewareConfig as a varinterval.Config. +// +// The contents of the config are not checked. +func newConfig(mwcfg chihaya.MiddlewareConfig) (*Config, error) { + bytes, err := yaml.Marshal(mwcfg.Config) + if err != nil { + return nil, err + } + + var cfg Config + err = yaml.Unmarshal(bytes, &cfg) + if err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/middleware/varinterval/config_test.go b/middleware/varinterval/config_test.go new file mode 100644 index 0000000..f43c73d --- /dev/null +++ b/middleware/varinterval/config_test.go @@ -0,0 +1,59 @@ +// Copyright 2016 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +package varinterval + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + + "github.com/chihaya/chihaya" +) + +type configTestData struct { + modifyProbability string + maxIncreaseDelta string + modifyMinInterval string + err bool + expected Config +} + +var ( + configTemplate = ` +name: foo +config: + modify_response_probability: %s + max_increase_delta: %s + modify_min_interval: %s` + + configData = []configTestData{ + {"1.0", "60", "false", false, Config{1.0, 60, false}}, + {"a", "60", "false", true, Config{}}, + } +) + +func TestNewConfig(t *testing.T) { + var mwconfig chihaya.MiddlewareConfig + + cfg, err := newConfig(mwconfig) + assert.Nil(t, err) + assert.NotNil(t, cfg) + + for _, test := range configData { + config := fmt.Sprintf(configTemplate, test.modifyProbability, test.maxIncreaseDelta, test.modifyMinInterval) + err = yaml.Unmarshal([]byte(config), &mwconfig) + assert.Nil(t, err) + + cfg, err = newConfig(mwconfig) + if test.err { + assert.NotNil(t, err) + continue + } + assert.Nil(t, err) + assert.Equal(t, test.expected, *cfg) + } +} diff --git a/middleware/varinterval/varinterval.go b/middleware/varinterval/varinterval.go new file mode 100644 index 0000000..2f8893e --- /dev/null +++ b/middleware/varinterval/varinterval.go @@ -0,0 +1,70 @@ +// Copyright 2016 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +package varinterval + +import ( + "errors" + "math/rand" + "time" + + "github.com/chihaya/chihaya" + "github.com/chihaya/chihaya/tracker" +) + +func init() { + tracker.RegisterAnnounceMiddlewareConstructor("varinterval", constructor) +} + +type varintervalMiddleware struct { + cfg *Config + r *rand.Rand +} + +// constructor provides a middleware constructor that returns a middleware to +// insert a variation into announce intervals. +// +// It returns an error if the config provided is either syntactically or +// semantically incorrect. +func constructor(c chihaya.MiddlewareConfig) (tracker.AnnounceMiddleware, error) { + cfg, err := newConfig(c) + if err != nil { + return nil, err + } + + if cfg.ModifyResponseProbability <= 0 || cfg.ModifyResponseProbability > 1 { + return nil, errors.New("modify_response_probability must be in [0,1)") + } + + if cfg.MaxIncreaseDelta <= 0 { + return nil, errors.New("max_increase_delta must be > 0") + } + + mw := varintervalMiddleware{ + cfg: cfg, + r: rand.New(rand.NewSource(time.Now().UnixNano())), + } + + return mw.modifyResponse, nil +} + +func (mw *varintervalMiddleware) modifyResponse(next tracker.AnnounceHandler) tracker.AnnounceHandler { + return func(cfg *chihaya.TrackerConfig, req *chihaya.AnnounceRequest, resp *chihaya.AnnounceResponse) error { + err := next(cfg, req, resp) + if err != nil { + return err + } + + if mw.cfg.ModifyResponseProbability == 1 || mw.r.Float32() < mw.cfg.ModifyResponseProbability { + addSeconds := time.Duration(mw.r.Intn(mw.cfg.MaxIncreaseDelta)+1) * time.Second + resp.Interval += addSeconds + + if mw.cfg.ModifyMinInterval { + resp.MinInterval += addSeconds + } + } + + return nil + } +} diff --git a/middleware/varinterval/varinterval_test.go b/middleware/varinterval/varinterval_test.go new file mode 100644 index 0000000..1f70949 --- /dev/null +++ b/middleware/varinterval/varinterval_test.go @@ -0,0 +1,66 @@ +// Copyright 2016 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +package varinterval + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/chihaya/chihaya" + "github.com/chihaya/chihaya/tracker" +) + +type constructorTestData struct { + cfg Config + error bool +} + +var constructorData = []constructorTestData{ + {Config{1.0, 10, false}, false}, + {Config{1.1, 10, false}, true}, + {Config{0, 10, true}, true}, + {Config{1.0, 0, false}, true}, +} + +func TestConstructor(t *testing.T) { + for _, tt := range constructorData { + _, err := constructor(chihaya.MiddlewareConfig{ + Config: tt.cfg, + }) + + if tt.error { + assert.NotNil(t, err, fmt.Sprintf("error expected for %+v", tt.cfg)) + } else { + assert.Nil(t, err, fmt.Sprintf("no error expected for %+v", tt.cfg)) + } + } +} + +func TestModifyResponse(t *testing.T) { + var ( + achain tracker.AnnounceChain + req chihaya.AnnounceRequest + resp chihaya.AnnounceResponse + ) + + mw, err := constructor(chihaya.MiddlewareConfig{ + Config: Config{ + ModifyResponseProbability: 1.0, + MaxIncreaseDelta: 10, + ModifyMinInterval: true, + }, + }) + assert.Nil(t, err) + + achain.Append(mw) + handler := achain.Handler() + + err = handler(nil, &req, &resp) + assert.Nil(t, err) + assert.True(t, resp.Interval > 0, "interval should have been increased") + assert.True(t, resp.MinInterval > 0, "min_interval should have been increased") +}