package p2p import ( "math/rand" "net" "sync" "time" cfg "github.com/tendermint/go-config" ) //-------------------------------------------------------- // delay reads/writes // randomly drop reads/writes // randomly drop connections const ( FuzzModeDrop = "drop" FuzzModeDelay = "delay" ) func FuzzConn(config cfg.Config, conn net.Conn) net.Conn { return &FuzzedConnection{ conn: conn, start: time.After(time.Second * 10), // so we have time to do peer handshakes and get set up params: config, } } type FuzzedConnection struct { conn net.Conn mtx sync.Mutex fuzz bool // we don't start fuzzing right away start <-chan time.Time // fuzz params params cfg.Config } func (fc *FuzzedConnection) randomDuration() time.Duration { return time.Millisecond * time.Duration(rand.Int()%fc.MaxDelayMilliseconds()) } func (fc *FuzzedConnection) Active() bool { return fc.params.GetBool(configFuzzActive) } func (fc *FuzzedConnection) Mode() string { return fc.params.GetString(configFuzzMode) } func (fc *FuzzedConnection) ProbDropRW() float64 { return fc.params.GetFloat64(configFuzzProbDropRW) } func (fc *FuzzedConnection) ProbDropConn() float64 { return fc.params.GetFloat64(configFuzzProbDropConn) } func (fc *FuzzedConnection) ProbSleep() float64 { return fc.params.GetFloat64(configFuzzProbSleep) } func (fc *FuzzedConnection) MaxDelayMilliseconds() int { return fc.params.GetInt(configFuzzMaxDelayMilliseconds) } // implements the fuzz (delay, kill conn) // and returns whether or not the read/write should be ignored func (fc *FuzzedConnection) Fuzz() bool { if !fc.shouldFuzz() { return false } switch fc.Mode() { case FuzzModeDrop: // randomly drop the r/w, drop the conn, or sleep r := rand.Float64() if r <= fc.ProbDropRW() { return true } else if r < fc.ProbDropRW()+fc.ProbDropConn() { // XXX: can't this fail because machine precision? // XXX: do we need an error? fc.Close() return true } else if r < fc.ProbDropRW()+fc.ProbDropConn()+fc.ProbSleep() { time.Sleep(fc.randomDuration()) } case FuzzModeDelay: // sleep a bit time.Sleep(fc.randomDuration()) } return false } // we don't fuzz until start chan fires func (fc *FuzzedConnection) shouldFuzz() bool { if !fc.Active() { return false } fc.mtx.Lock() defer fc.mtx.Unlock() if fc.fuzz { return true } select { case <-fc.start: fc.fuzz = true default: } return false } func (fc *FuzzedConnection) Read(data []byte) (n int, err error) { if fc.Fuzz() { return 0, nil } return fc.conn.Read(data) } func (fc *FuzzedConnection) Write(data []byte) (n int, err error) { if fc.Fuzz() { return 0, nil } return fc.conn.Write(data) } // Implements net.Conn func (fc *FuzzedConnection) Close() error { return fc.conn.Close() } func (fc *FuzzedConnection) LocalAddr() net.Addr { return fc.conn.LocalAddr() } func (fc *FuzzedConnection) RemoteAddr() net.Addr { return fc.conn.RemoteAddr() } func (fc *FuzzedConnection) SetDeadline(t time.Time) error { return fc.conn.SetDeadline(t) } func (fc *FuzzedConnection) SetReadDeadline(t time.Time) error { return fc.conn.SetReadDeadline(t) } func (fc *FuzzedConnection) SetWriteDeadline(t time.Time) error { return fc.conn.SetWriteDeadline(t) }