diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a784b5..63b60d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ /usr/bin/toxiproxy-server ``` (#331, @miry) +* A new toxic to simulate TCP RESET (Connection reset by peer) on the connections by closing + the stub Input immediately or after a timeout. (#247 and #333, @chaosbox) # [2.1.7] diff --git a/README.md b/README.md index 1a0f42a7..ef3e2b01 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ stopping you from creating a client in any other language (see 3. [Bandwidth](#bandwidth) 4. [Slow close](#slow_close) 5. [Timeout](#timeout) - 6. [Slicer](#slicer) + 6. [Reset peer](#reset_peer) + 7. [Slicer](#slicer) 6. [HTTP API](#http-api) 1. [Proxy fields](#proxy-fields) 2. [Toxic fields](#toxic-fields) @@ -404,6 +405,15 @@ Stops all data from getting through, and closes the connection after `timeout`. `timeout` is 0, the connection won't close, and data will be delayed until the toxic is removed. +Attributes: + + - `timeout`: time in milliseconds + +#### reset_peer + +Simulate TCP RESET (Connection reset by peer) on the connections by closing the stub Input +immediately or after a `timeout`. + Attributes: - `timeout`: time in milliseconds diff --git a/bin/e2e b/bin/e2e index 76e30696..6a1d889e 100755 --- a/bin/e2e +++ b/bin/e2e @@ -113,6 +113,16 @@ go test -bench=. ./testing -v echo -e "-----------------\n" +echo "=== Reset peer toxic" + +./dist/toxiproxy-cli toxic add --type reset_peer --toxicName "reset_peer" \ + --attribute "timeout=2000" \ + --toxicity 1.0 shopify_http +./dist/toxiproxy-cli inspect shopify_http +./dist/toxiproxy-cli toxic delete --toxicName "reset_peer" shopify_http + +echo -e "-----------------\n" + echo "== Teardown" ./dist/toxiproxy-cli delete shopify_http diff --git a/cli/cli.go b/cli/cli.go index 3ac9a880..4dee3288 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -44,6 +44,10 @@ var toxicDescription = ` timeout: stop all data and close after timeout timeout= + reset_peer: simulate TCP RESET (Connection reset by peer) on the connections by closing + the stub Input immediately or after a timeout + timeout= + slicer: slice data into bits with optional delay average_size=,size_variation=,delay= diff --git a/link.go b/link.go index 8795f34e..08e09597 100644 --- a/link.go +++ b/link.go @@ -64,7 +64,6 @@ func NewToxicLink( // Start the link with the specified toxics. func (link *ToxicLink) Start(name string, source io.Reader, dest io.WriteCloser) { - go func() { bytes, err := io.Copy(link.input, source) if err != nil { @@ -76,31 +75,33 @@ func (link *ToxicLink) Start(name string, source io.Reader, dest io.WriteCloser) } link.input.Close() }() + for i, toxic := range link.toxics.chain[link.direction] { if stateful, ok := toxic.Toxic.(toxics.StatefulToxic); ok { link.stubs[i].State = stateful.NewState() } + if _, ok := toxic.Toxic.(*toxics.ResetToxic); ok { if err := source.(*net.TCPConn).SetLinger(0); err != nil { logrus.WithFields(logrus.Fields{ "name": link.proxy.Name, "toxic": toxic.Type, "err": err, - }).Error("source: Unable to setLinger(ms)") - + }).Error("source: Unable to setLinger(ms)") } + if err := dest.(*net.TCPConn).SetLinger(0); err != nil { logrus.WithFields(logrus.Fields{ "name": link.proxy.Name, "toxic": toxic.Type, "err": err, - }).Error("dest: Unable to setLinger(ms)") - + }).Error("dest: Unable to setLinger(ms)") } } go link.stubs[i].Run(toxic) } + go func() { bytes, err := io.Copy(dest, link.output) if err != nil { diff --git a/toxics/reset_peer.go b/toxics/reset_peer.go index 553ec03f..6b19dde4 100644 --- a/toxics/reset_peer.go +++ b/toxics/reset_peer.go @@ -5,7 +5,8 @@ import ( ) /* -The ResetToxic sends closes the connection abruptly after a timeout (in ms). The behaviour of Close is set to discard any unsent/unacknowledged data by setting SetLinger to 0, +The ResetToxic sends closes the connection abruptly after a timeout (in ms). +The behavior of Close is set to discard any unsent/unacknowledged data by setting SetLinger to 0, ~= sets TCP RST flag and resets the connection. If the timeout is set to 0, then the connection will be reset immediately. diff --git a/toxics/reset_peer_test.go b/toxics/reset_peer_test.go index f6a7d36c..57630213 100644 --- a/toxics/reset_peer_test.go +++ b/toxics/reset_peer_test.go @@ -2,13 +2,14 @@ package toxics_test import ( "bufio" - "github.com/Shopify/toxiproxy/toxics" "io" "net" "os" "syscall" "testing" "time" + + "github.com/Shopify/toxiproxy/v2/toxics" ) const msg = "reset toxic payload\n" @@ -21,15 +22,24 @@ func TestResetToxicWithTimeout(t *testing.T) { start := time.Now() resetToxic := toxics.ResetToxic{Timeout: 100} resetTCPHelper(t, ToxicToJson(t, "resettcp", "reset_peer", "upstream", &resetToxic)) - AssertDeltaTime(t, "Reset after timeout", time.Since(start), time.Duration(resetToxic.Timeout)*time.Millisecond, time.Duration(resetToxic.Timeout+10)*time.Millisecond) + AssertDeltaTime(t, + "Reset after timeout", + time.Since(start), + time.Duration(resetToxic.Timeout)*time.Millisecond, + time.Duration(resetToxic.Timeout+10)*time.Millisecond, + ) } func TestResetToxicWithTimeoutDownstream(t *testing.T) { start := time.Now() resetToxic := toxics.ResetToxic{Timeout: 100} resetTCPHelper(t, ToxicToJson(t, "resettcp", "reset_peer", "downstream", &resetToxic)) - AssertDeltaTime(t, "Reset after timeout", time.Since(start), time.Duration(resetToxic.Timeout)*time.Millisecond, time.Duration(resetToxic.Timeout+10)*time.Millisecond) - + AssertDeltaTime(t, + "Reset after timeout", + time.Since(start), + time.Duration(resetToxic.Timeout)*time.Millisecond, + time.Duration(resetToxic.Timeout+10)*time.Millisecond, + ) } func checkConnectionState(t *testing.T, listenAddress string) { @@ -49,7 +59,10 @@ func checkConnectionState(t *testing.T, listenAddress string) { t.Error("Expected: connection reset by peer. Got:", err) } } else { - t.Error("Expected: connection reset by peer. Got:", err, "conn:", conn.RemoteAddr(), conn.LocalAddr()) + t.Error( + "Expected: connection reset by peer. Got:", + err, "conn:", conn.RemoteAddr(), conn.LocalAddr(), + ) } _, err = conn.Read(tmp) if err != io.EOF {