Skip to content

Commit 64cb722

Browse files
authored
Bugfix: don't stop proxies when using hostnames to specify listen addresses (#631)
* Bugfix: don't stop proxies when using hostnames to specify listen addresses Before this change we'd stop existing proxies when populating a set of proxies when the listen addresses were specified using hostnames instead of IP addresses. This is very surprising as we encourage users to populate proxies at the begin of an application start: > A /populate call can be included for example at application start to ensure > all required proxies exist. It is safe to make this call several times, since > proxies will be untouched as long as their fields are consistent with the new > data. This is (currently) only partially true as it doesn't work when using hostnames. For upstream services this isn't a problem because we never store the resolved address. The change introduces a "Differs" function on a proxy to determine if two proxies are different in terms of listening address and upstream service. This is now used both when changing collections as well as updating individual proxies. Closes #131. * Update changelog
1 parent 9e42101 commit 64cb722

File tree

5 files changed

+118
-9
lines changed

5 files changed

+118
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# [Unreleased]
22

33
- Update go version to 1.23.0 (#628)
4+
- Do not restart proxies when using hostnames to specify listen address when updating a proxy
5+
and populating a collection (#631, @robinbrandt)
46

57
# [2.11.0] - 2024-10-16
68

api_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ func TestPopulateExistingProxy(t *testing.T) {
256256
if err != nil {
257257
t.Fatal("Unable to create proxy:", err)
258258
}
259+
259260
_, err = client.CreateProxy("two", "localhost:7373", "localhost:7474")
260261
if err != nil {
261262
t.Fatal("Unable to create proxy:", err)
@@ -270,7 +271,7 @@ func TestPopulateExistingProxy(t *testing.T) {
270271
testProxies, err := client.Populate([]tclient.Proxy{
271272
{
272273
Name: "one",
273-
Listen: "127.0.0.1:7070",
274+
Listen: "localhost:7070", // intentional: this should be resolved to 127.0.0.1:7070
274275
Upstream: "localhost:7171",
275276
Enabled: true,
276277
},

proxy.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ func (proxy *Proxy) Update(input *Proxy) error {
8181
proxy.Lock()
8282
defer proxy.Unlock()
8383

84-
if input.Listen != proxy.Listen || input.Upstream != proxy.Upstream {
84+
differs, err := proxy.Differs(input)
85+
if err != nil {
86+
return err
87+
}
88+
89+
if differs {
8590
stop(proxy)
8691
proxy.Listen = input.Listen
8792
proxy.Upstream = input.Upstream
@@ -131,6 +136,19 @@ func (proxy *Proxy) close() {
131136
}
132137
}
133138

139+
func (proxy *Proxy) Differs(other *Proxy) (bool, error) {
140+
newResolvedListen, err := net.ResolveTCPAddr("tcp", other.Listen)
141+
if err != nil {
142+
return false, err
143+
}
144+
145+
if proxy.Listen != newResolvedListen.String() || proxy.Upstream != other.Upstream {
146+
return true, nil
147+
}
148+
149+
return false, nil
150+
}
151+
134152
// This channel is to kill the blocking Accept() call below by closing the
135153
// net.Listener.
136154
func (proxy *Proxy) freeBlocker(acceptTomb *tomb.Tomb) {

proxy_collection.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,32 @@ func (collection *ProxyCollection) Add(proxy *Proxy, start bool) error {
4343
return nil
4444
}
4545

46-
func (collection *ProxyCollection) AddOrReplace(proxy *Proxy, start bool) error {
46+
func (collection *ProxyCollection) AddOrReplace(proxy *Proxy, start bool) (*Proxy, error) {
4747
collection.Lock()
4848
defer collection.Unlock()
4949

5050
if existing, exists := collection.proxies[proxy.Name]; exists {
51-
if existing.Listen == proxy.Listen && existing.Upstream == proxy.Upstream {
52-
return nil
51+
differs, err := existing.Differs(proxy)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
if !differs {
57+
return existing, nil
5358
}
5459
existing.Stop()
5560
}
5661

5762
if start {
5863
err := proxy.Start()
5964
if err != nil {
60-
return err
65+
return nil, err
6166
}
6267
}
6368

6469
collection.proxies[proxy.Name] = proxy
6570

66-
return nil
71+
return proxy, nil
6772
}
6873

6974
func (collection *ProxyCollection) PopulateJson(
@@ -98,12 +103,12 @@ func (collection *ProxyCollection) PopulateJson(
98103

99104
for i := range input {
100105
proxy := NewProxy(server, input[i].Name, input[i].Listen, input[i].Upstream)
101-
err = collection.AddOrReplace(proxy, *input[i].Enabled)
106+
addedOrReplaced, err := collection.AddOrReplace(proxy, *input[i].Enabled)
102107
if err != nil {
103108
return proxies, err
104109
}
105110

106-
proxies = append(proxies, proxy)
111+
proxies = append(proxies, addedOrReplaced)
107112
}
108113
return proxies, err
109114
}

proxy_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package toxiproxy_test
33
import (
44
"bytes"
55
"encoding/hex"
6+
"errors"
67
"io"
78
"net"
9+
"os"
810
"testing"
911
"time"
1012

@@ -177,6 +179,62 @@ func TestProxyUpdate(t *testing.T) {
177179
})
178180
}
179181

182+
func TestProxyUpdateWithHostname(t *testing.T) {
183+
testhelper.WithTCPServer(t, func(upstream string, response chan []byte) {
184+
proxy := NewTestProxy("test", upstream)
185+
err := proxy.Start()
186+
if err != nil {
187+
t.Error("Proxy failed to start", err)
188+
}
189+
AssertProxyUp(t, proxy.Listen, true)
190+
191+
connectionLost := make(chan bool)
192+
193+
// Start a goroutine to check if connection is maintained
194+
go func() {
195+
conn, err := net.Dial("tcp", proxy.Listen)
196+
if err != nil {
197+
t.Error("Failed to connect to proxy", err)
198+
}
199+
defer conn.Close()
200+
201+
// Try to read from the connection
202+
buf := make([]byte, 1024)
203+
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
204+
_, err = conn.Read(buf)
205+
if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
206+
connectionLost <- true
207+
return
208+
}
209+
210+
connectionLost <- false
211+
}()
212+
213+
_, port, err := net.SplitHostPort(proxy.Listen)
214+
if err != nil {
215+
t.Error("Failed to split host and port", err)
216+
}
217+
218+
input := &toxiproxy.Proxy{
219+
Listen: net.JoinHostPort("localhost", port),
220+
Upstream: proxy.Upstream,
221+
Enabled: true,
222+
}
223+
err = proxy.Update(input)
224+
if err != nil {
225+
t.Error("Failed to update proxy", err)
226+
}
227+
228+
// Check if the connection was lost during the update
229+
if lost := <-connectionLost; lost {
230+
t.Error("Connection was lost during proxy update")
231+
}
232+
233+
// Verify proxy is still up after the update
234+
AssertProxyUp(t, proxy.Listen, true)
235+
})
236+
}
237+
180238
func TestRestartFailedToStartProxy(t *testing.T) {
181239
testhelper.WithTCPServer(t, func(upstream string, response chan []byte) {
182240
proxy := NewTestProxy("test", upstream)
@@ -207,3 +265,28 @@ func TestRestartFailedToStartProxy(t *testing.T) {
207265
AssertProxyUp(t, proxy.Listen, false)
208266
})
209267
}
268+
269+
func TestProxyDiffers(t *testing.T) {
270+
testhelper.WithTCPServer(t, func(upstream string, response chan []byte) {
271+
proxy := NewTestProxy("test", upstream)
272+
proxy.Start()
273+
_, port, err := net.SplitHostPort(proxy.Listen)
274+
if err != nil {
275+
t.Error("Failed to split host and port", err)
276+
}
277+
otherProxy := &toxiproxy.Proxy{
278+
Name: "other",
279+
Listen: net.JoinHostPort("localhost", port),
280+
Upstream: upstream,
281+
Enabled: true,
282+
}
283+
284+
differs, err := proxy.Differs(otherProxy)
285+
if err != nil {
286+
t.Error("Failed to check if proxy differs", err)
287+
}
288+
if differs {
289+
t.Error("Proxy should not differ ")
290+
}
291+
})
292+
}

0 commit comments

Comments
 (0)