Browse code

cmd/docker-proxy: pass open listener to proxy impl

In preparation for the daemon passing a listen fd, add command line
option -use-listen-fd to indicate that the fd is present (as fd 4).

If the new flag isn't given, open the listener as normal.

Refactor the TCP and UDP proxies to be constructed with an existing
TCPListener or UDPConn, respectively. Lift the responsibilty of opening
the listener to the entrypoint. Per the Single Responsibility Principle,
this structure affords changing how the listener is created without
having to touch the proxy implementations.

Co-authored-by: Cory Snider <csnider@mirantis.com>
Signed-off-by: Rob Murray <rob.murray@docker.com>

Rob Murray authored on 2024/07/08 21:50:22
Showing 6 changed files
... ...
@@ -1,9 +1,9 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"flag"
5 6
 	"fmt"
6
-	"log"
7 7
 	"net"
8 8
 	"os"
9 9
 	"os/signal"
... ...
@@ -13,11 +13,27 @@ import (
13 13
 	"github.com/ishidawataru/sctp"
14 14
 )
15 15
 
16
+// The caller is expected to pass-in open file descriptors ...
17
+const (
18
+	// Pipe for reporting status, as a string. "0\n" if the proxy
19
+	// started normally. "1\n<error message>" otherwise.
20
+	parentPipeFd uintptr = 3 + iota
21
+	// If -use-listen-fd=true, a listening socket ready to accept TCP
22
+	// connections or receive UDP. (Without that option on the command
23
+	// line, the listener needs to be opened by docker-proxy, for
24
+	// compatibility with older docker daemons. In this case fd 4
25
+	// may belong to the Go runtime.)
26
+	listenSockFd
27
+)
28
+
16 29
 func main() {
17
-	f := os.NewFile(3, "signal-parent")
18
-	host, container := parseFlags()
30
+	config := parseFlags()
31
+	p, err := newProxy(config)
32
+	if config.ListenSock != nil {
33
+		config.ListenSock.Close()
34
+	}
19 35
 
20
-	p, err := NewProxy(host, container)
36
+	f := os.NewFile(parentPipeFd, "signal-parent")
21 37
 	if err != nil {
22 38
 		fmt.Fprintf(f, "1\n%s", err)
23 39
 		f.Close()
... ...
@@ -31,41 +47,114 @@ func main() {
31 31
 	p.Run()
32 32
 }
33 33
 
34
+func newProxy(config ProxyConfig) (p Proxy, err error) {
35
+	ipv := ipv4
36
+	if config.HostIP.To4() == nil {
37
+		ipv = ipv6
38
+	}
39
+
40
+	switch config.Proto {
41
+	case "tcp":
42
+		var listener *net.TCPListener
43
+		if config.ListenSock == nil {
44
+			// Fall back to HostIP:HostPort if no socket on fd 4, for compatibility with older daemons.
45
+			hostAddr := &net.TCPAddr{IP: config.HostIP, Port: config.HostPort}
46
+			listener, err = net.ListenTCP("tcp"+string(ipv), hostAddr)
47
+			if err != nil {
48
+				return nil, fmt.Errorf("failed to listen on %s: %w", hostAddr, err)
49
+			}
50
+		} else {
51
+			l, err := net.FileListener(config.ListenSock)
52
+			if err != nil {
53
+				return nil, err
54
+			}
55
+			var ok bool
56
+			listener, ok = l.(*net.TCPListener)
57
+			if !ok {
58
+				return nil, fmt.Errorf("unexpected socket type for listener fd: %s", l.Addr().Network())
59
+			}
60
+		}
61
+		container := &net.TCPAddr{IP: config.ContainerIP, Port: config.ContainerPort}
62
+		p, err = NewTCPProxy(listener, container)
63
+	case "udp":
64
+		var listener *net.UDPConn
65
+		if config.ListenSock == nil {
66
+			// Fall back to HostIP:HostPort if no socket on fd 4, for compatibility with older daemons.
67
+			hostAddr := &net.UDPAddr{IP: config.HostIP, Port: config.HostPort}
68
+			listener, err = net.ListenUDP("udp"+string(ipv), hostAddr)
69
+			if err != nil {
70
+				return nil, fmt.Errorf("failed to listen on %s: %w", hostAddr, err)
71
+			}
72
+		} else {
73
+			l, err := net.FilePacketConn(config.ListenSock)
74
+			if err != nil {
75
+				return nil, err
76
+			}
77
+			var ok bool
78
+			listener, ok = l.(*net.UDPConn)
79
+			if !ok {
80
+				return nil, fmt.Errorf("unexpected socket type for listener fd: %s", l.LocalAddr().Network())
81
+			}
82
+		}
83
+		container := &net.UDPAddr{IP: config.ContainerIP, Port: config.ContainerPort}
84
+		p, err = NewUDPProxy(listener, container)
85
+	case "sctp":
86
+		var listener *sctp.SCTPListener
87
+		if config.ListenSock != nil {
88
+			// There's no way to construct an SCTPListener from a file descriptor at the moment.
89
+			// If a socket has been passed in, it's probably from a newer daemon using a version
90
+			// of the sctp module that does allow it.
91
+			return nil, errors.New("cannot use supplied SCTP socket, check the latest docker-proxy is in your $PATH")
92
+		}
93
+		hostAddr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: config.HostIP}}, Port: config.HostPort}
94
+		container := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: config.ContainerIP}}, Port: config.ContainerPort}
95
+		listener, err = sctp.ListenSCTP("sctp"+string(ipv), hostAddr)
96
+		if err != nil {
97
+			return nil, fmt.Errorf("failed to listen on %s: %w", hostAddr, err)
98
+		}
99
+		p, err = NewSCTPProxy(listener, container)
100
+	default:
101
+		return nil, fmt.Errorf("unsupported protocol %s", config.Proto)
102
+	}
103
+
104
+	return p, err
105
+}
106
+
107
+type ProxyConfig struct {
108
+	Proto                   string
109
+	HostIP, ContainerIP     net.IP
110
+	HostPort, ContainerPort int
111
+	ListenSock              *os.File
112
+}
113
+
34 114
 // parseFlags parses the flags passed on reexec to create the TCP/UDP/SCTP
35 115
 // net.Addrs to map the host and container ports.
36
-func parseFlags() (host net.Addr, container net.Addr) {
116
+func parseFlags() ProxyConfig {
37 117
 	var (
38
-		proto         = flag.String("proto", "tcp", "proxy protocol")
39
-		hostIP        = flag.String("host-ip", "", "host ip")
40
-		hostPort      = flag.Int("host-port", -1, "host port")
41
-		containerIP   = flag.String("container-ip", "", "container ip")
42
-		containerPort = flag.Int("container-port", -1, "container port")
43
-		printVer      = flag.Bool("v", false, "print version information and quit")
44
-		printVersion  = flag.Bool("version", false, "print version information and quit")
118
+		config      ProxyConfig
119
+		useListenFd bool
120
+		printVer    bool
45 121
 	)
46
-
122
+	flag.StringVar(&config.Proto, "proto", "tcp", "proxy protocol")
123
+	flag.TextVar(&config.HostIP, "host-ip", net.IPv4zero, "host ip")
124
+	flag.IntVar(&config.HostPort, "host-port", -1, "host port")
125
+	flag.TextVar(&config.ContainerIP, "container-ip", net.IPv4zero, "container ip")
126
+	flag.IntVar(&config.ContainerPort, "container-port", -1, "container port")
127
+	flag.BoolVar(&useListenFd, "use-listen-fd", false, "use a supplied listen fd")
128
+	flag.BoolVar(&printVer, "v", false, "print version information and quit")
129
+	flag.BoolVar(&printVer, "version", false, "print version information and quit")
47 130
 	flag.Parse()
48 131
 
49
-	if *printVer || *printVersion {
132
+	if printVer {
50 133
 		fmt.Printf("docker-proxy (commit %s) version %s\n", dockerversion.GitCommit, dockerversion.Version)
51 134
 		os.Exit(0)
52 135
 	}
53 136
 
54
-	switch *proto {
55
-	case "tcp":
56
-		host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
57
-		container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
58
-	case "udp":
59
-		host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
60
-		container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
61
-	case "sctp":
62
-		host = &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: net.ParseIP(*hostIP)}}, Port: *hostPort}
63
-		container = &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: net.ParseIP(*containerIP)}}, Port: *containerPort}
64
-	default:
65
-		log.Fatalf("unsupported protocol %s", *proto)
137
+	if useListenFd {
138
+		config.ListenSock = os.NewFile(listenSockFd, "listen-sock")
66 139
 	}
67 140
 
68
-	return host, container
141
+	return config
69 142
 }
70 143
 
71 144
 func handleStopSignals(p Proxy) {
... ...
@@ -1,3 +1,5 @@
1
+//go:build !windows
2
+
1 3
 package main
2 4
 
3 5
 import (
... ...
@@ -5,13 +7,13 @@ import (
5 5
 	"fmt"
6 6
 	"io"
7 7
 	"net"
8
-	"runtime"
8
+	"os"
9 9
 	"strings"
10 10
 	"testing"
11 11
 	"time"
12 12
 
13 13
 	"github.com/ishidawataru/sctp"
14
-	"gotest.tools/v3/skip"
14
+	"gotest.tools/v3/assert"
15 15
 )
16 16
 
17 17
 var (
... ...
@@ -40,6 +42,8 @@ type UDPEchoServer struct {
40 40
 	testCtx *testing.T
41 41
 }
42 42
 
43
+const hopefullyFreePort = 25587
44
+
43 45
 func NewEchoServer(t *testing.T, proto, address string, opts EchoServerOptions) EchoServer {
44 46
 	var server EchoServer
45 47
 	if !strings.HasPrefix(proto, "tcp") && opts.TCPHalfClose {
... ...
@@ -128,7 +132,31 @@ func (server *UDPEchoServer) Run() {
128 128
 func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() }
129 129
 func (server *UDPEchoServer) Close()              { server.conn.Close() }
130 130
 
131
+func tcpListener(t *testing.T, nw string, addr *net.TCPAddr) (*os.File, *net.TCPAddr) {
132
+	t.Helper()
133
+	l, err := net.ListenTCP(nw, addr)
134
+	assert.NilError(t, err)
135
+	osFile, err := l.File()
136
+	assert.NilError(t, err)
137
+	tcpAddr := l.Addr().(*net.TCPAddr)
138
+	err = l.Close()
139
+	assert.NilError(t, err)
140
+	return osFile, tcpAddr
141
+}
142
+
143
+func udpListener(t *testing.T, nw string, addr *net.UDPAddr) (*os.File, *net.UDPAddr) {
144
+	t.Helper()
145
+	l, err := net.ListenUDP(nw, addr)
146
+	assert.NilError(t, err)
147
+	osFile, err := l.File()
148
+	assert.NilError(t, err)
149
+	err = l.Close()
150
+	assert.NilError(t, err)
151
+	return osFile, l.LocalAddr().(*net.UDPAddr)
152
+}
153
+
131 154
 func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string, halfClose bool) {
155
+	t.Helper()
132 156
 	defer proxy.Close()
133 157
 	go proxy.Run()
134 158
 	var client net.Conn
... ...
@@ -167,98 +195,169 @@ func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string, halfClose
167 167
 	}
168 168
 }
169 169
 
170
-func testProxy(t *testing.T, proto string, proxy Proxy, halfClose bool) {
171
-	testProxyAt(t, proto, proxy, proxy.FrontendAddr().String(), halfClose)
172
-}
173
-
174
-func testTCP4Proxy(t *testing.T, halfClose bool) {
170
+func testTCP4Proxy(t *testing.T, halfClose bool, hostPort int) {
171
+	t.Helper()
175 172
 	backend := NewEchoServer(t, "tcp", "127.0.0.1:0", EchoServerOptions{TCPHalfClose: halfClose})
176 173
 	defer backend.Close()
177 174
 	backend.Run()
175
+	backendAddr := backend.LocalAddr().(*net.TCPAddr)
176
+	var listener *os.File
178 177
 	frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
179
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
178
+	if hostPort == 0 {
179
+		listener, frontendAddr = tcpListener(t, "tcp4", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
180
+	} else {
181
+		frontendAddr.Port = hostPort
182
+	}
183
+	config := ProxyConfig{
184
+		Proto:         "tcp",
185
+		HostIP:        frontendAddr.IP,
186
+		HostPort:      frontendAddr.Port,
187
+		ContainerIP:   backendAddr.IP,
188
+		ContainerPort: backendAddr.Port,
189
+		ListenSock:    listener,
190
+	}
191
+	proxy, err := newProxy(config)
180 192
 	if err != nil {
181 193
 		t.Fatal(err)
182 194
 	}
183
-	testProxy(t, "tcp", proxy, halfClose)
195
+	testProxyAt(t, "tcp", proxy, frontendAddr.String(), halfClose)
184 196
 }
185 197
 
186 198
 func TestTCP4Proxy(t *testing.T) {
187
-	testTCP4Proxy(t, false)
199
+	testTCP4Proxy(t, false, 0)
200
+}
201
+
202
+func TestTCP4ProxyNoListener(t *testing.T) {
203
+	testTCP4Proxy(t, false, hopefullyFreePort)
188 204
 }
189 205
 
190 206
 func TestTCP4ProxyHalfClose(t *testing.T) {
191
-	testTCP4Proxy(t, true)
207
+	testTCP4Proxy(t, true, 0)
192 208
 }
193 209
 
194 210
 func TestTCP6Proxy(t *testing.T) {
195
-	t.Skip("Need to start CI docker with --ipv6")
196 211
 	backend := NewEchoServer(t, "tcp", "[::1]:0", EchoServerOptions{})
197 212
 	defer backend.Close()
198 213
 	backend.Run()
199
-	frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
200
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
214
+	backendAddr := backend.LocalAddr().(*net.TCPAddr)
215
+	listener, frontendAddr := tcpListener(t, "tcp6", &net.TCPAddr{IP: net.IPv6loopback, Port: 0})
216
+	config := ProxyConfig{
217
+		Proto:         "tcp",
218
+		HostIP:        frontendAddr.IP,
219
+		HostPort:      frontendAddr.Port,
220
+		ContainerIP:   backendAddr.IP,
221
+		ContainerPort: backendAddr.Port,
222
+		ListenSock:    listener,
223
+	}
224
+	proxy, err := newProxy(config)
201 225
 	if err != nil {
202 226
 		t.Fatal(err)
203 227
 	}
204
-	testProxy(t, "tcp", proxy, false)
228
+	testProxyAt(t, "tcp", proxy, frontendAddr.String(), false)
205 229
 }
206 230
 
207 231
 func TestTCPDualStackProxy(t *testing.T) {
208
-	// If I understand `godoc -src net favoriteAddrFamily` (used by the
209
-	// net.Listen* functions) correctly this should work, but it doesn't.
210
-	t.Skip("No support for dual stack yet")
211 232
 	backend := NewEchoServer(t, "tcp", "[::1]:0", EchoServerOptions{})
212 233
 	defer backend.Close()
213 234
 	backend.Run()
214
-	frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
215
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
235
+	backendAddr := backend.LocalAddr().(*net.TCPAddr)
236
+	listener, frontendAddr := tcpListener(t, "tcp", &net.TCPAddr{IP: net.IPv6zero, Port: 0})
237
+	config := ProxyConfig{
238
+		Proto:         "tcp",
239
+		HostIP:        frontendAddr.IP,
240
+		HostPort:      frontendAddr.Port,
241
+		ContainerIP:   backendAddr.IP,
242
+		ContainerPort: backendAddr.Port,
243
+		ListenSock:    listener,
244
+	}
245
+	proxy, err := newProxy(config)
216 246
 	if err != nil {
217 247
 		t.Fatal(err)
218 248
 	}
219 249
 	ipv4ProxyAddr := &net.TCPAddr{
220 250
 		IP:   net.IPv4(127, 0, 0, 1),
221
-		Port: proxy.FrontendAddr().(*net.TCPAddr).Port,
251
+		Port: frontendAddr.Port,
222 252
 	}
223 253
 	testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String(), false)
224 254
 }
225 255
 
226
-func TestUDP4Proxy(t *testing.T) {
256
+func testUDP4Proxy(t *testing.T, hostPort int) {
257
+	t.Helper()
227 258
 	backend := NewEchoServer(t, "udp", "127.0.0.1:0", EchoServerOptions{})
228 259
 	defer backend.Close()
229 260
 	backend.Run()
261
+	var listener *os.File
230 262
 	frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
231
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
263
+	if hostPort == 0 {
264
+		listener, frontendAddr = udpListener(t, "udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
265
+	} else {
266
+		frontendAddr.Port = hostPort
267
+	}
268
+	backendAddr := backend.LocalAddr().(*net.UDPAddr)
269
+	config := ProxyConfig{
270
+		Proto:         "udp",
271
+		HostIP:        frontendAddr.IP,
272
+		HostPort:      frontendAddr.Port,
273
+		ContainerIP:   backendAddr.IP,
274
+		ContainerPort: backendAddr.Port,
275
+		ListenSock:    listener,
276
+	}
277
+	proxy, err := newProxy(config)
232 278
 	if err != nil {
233 279
 		t.Fatal(err)
234 280
 	}
235
-	testProxy(t, "udp", proxy, false)
281
+	testProxyAt(t, "udp", proxy, frontendAddr.String(), false)
282
+}
283
+
284
+func TestUDP4Proxy(t *testing.T) {
285
+	testUDP4Proxy(t, 0)
286
+}
287
+
288
+func TestUDP4ProxyNoListener(t *testing.T) {
289
+	testUDP4Proxy(t, hopefullyFreePort)
236 290
 }
237 291
 
238 292
 func TestUDP6Proxy(t *testing.T) {
239
-	t.Skip("Need to start CI docker with --ipv6")
240 293
 	backend := NewEchoServer(t, "udp", "[::1]:0", EchoServerOptions{})
241 294
 	defer backend.Close()
242 295
 	backend.Run()
243
-	frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0}
244
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
296
+	listener, frontendAddr := udpListener(t, "udp6", &net.UDPAddr{IP: net.IPv6loopback, Port: 0})
297
+	backendAddr := backend.LocalAddr().(*net.UDPAddr)
298
+	config := ProxyConfig{
299
+		Proto:         "udp",
300
+		HostIP:        frontendAddr.IP,
301
+		HostPort:      frontendAddr.Port,
302
+		ContainerIP:   backendAddr.IP,
303
+		ContainerPort: backendAddr.Port,
304
+		ListenSock:    listener,
305
+	}
306
+	proxy, err := newProxy(config)
245 307
 	if err != nil {
246 308
 		t.Fatal(err)
247 309
 	}
248
-	testProxy(t, "udp", proxy, false)
310
+	testProxyAt(t, "udp", proxy, frontendAddr.String(), false)
249 311
 }
250 312
 
251 313
 func TestUDPWriteError(t *testing.T) {
252 314
 	frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
253 315
 	// Hopefully, this port will be free: */
254
-	backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587}
255
-	proxy, err := NewProxy(frontendAddr, backendAddr)
316
+	backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: hopefullyFreePort}
317
+	listener, frontendAddr := udpListener(t, "udp4", frontendAddr)
318
+	config := ProxyConfig{
319
+		Proto:         "udp",
320
+		HostIP:        frontendAddr.IP,
321
+		HostPort:      frontendAddr.Port,
322
+		ContainerIP:   backendAddr.IP,
323
+		ContainerPort: backendAddr.Port,
324
+		ListenSock:    listener,
325
+	}
326
+	proxy, err := newProxy(config)
256 327
 	if err != nil {
257 328
 		t.Fatal(err)
258 329
 	}
259 330
 	defer proxy.Close()
260 331
 	go proxy.Run()
261
-	client, err := net.Dial("udp", "127.0.0.1:25587")
332
+	client, err := net.Dial("udp", frontendAddr.String())
262 333
 	if err != nil {
263 334
 		t.Fatalf("Can't connect to the proxy: %v", err)
264 335
 	}
... ...
@@ -266,7 +365,7 @@ func TestUDPWriteError(t *testing.T) {
266 266
 	// Make sure the proxy doesn't stop when there is no actual backend:
267 267
 	client.Write(testBuf)
268 268
 	client.Write(testBuf)
269
-	backend := NewEchoServer(t, "udp", "127.0.0.1:25587", EchoServerOptions{})
269
+	backend := NewEchoServer(t, "udp", backendAddr.String(), EchoServerOptions{})
270 270
 	defer backend.Close()
271 271
 	backend.Run()
272 272
 	client.SetDeadline(time.Now().Add(10 * time.Second))
... ...
@@ -282,31 +381,36 @@ func TestUDPWriteError(t *testing.T) {
282 282
 	}
283 283
 }
284 284
 
285
-func TestSCTP4Proxy(t *testing.T) {
286
-	skip.If(t, runtime.GOOS == "windows", "sctp is not supported on windows")
287
-
285
+func TestSCTP4ProxyNoListener(t *testing.T) {
288 286
 	backend := NewEchoServer(t, "sctp", "127.0.0.1:0", EchoServerOptions{})
289 287
 	defer backend.Close()
290 288
 	backend.Run()
291
-	frontendAddr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: net.IPv4(127, 0, 0, 1)}}, Port: 0}
292
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
293
-	if err != nil {
294
-		t.Fatal(err)
289
+	backendAddr := backend.LocalAddr().(*sctp.SCTPAddr)
290
+	config := ProxyConfig{
291
+		Proto:         "sctp",
292
+		HostIP:        net.IPv4(127, 0, 0, 1),
293
+		HostPort:      hopefullyFreePort,
294
+		ContainerIP:   backendAddr.IPAddrs[0].IP,
295
+		ContainerPort: backendAddr.Port,
295 296
 	}
296
-	testProxy(t, "sctp", proxy, false)
297
+	proxy, err := newProxy(config)
298
+	assert.NilError(t, err)
299
+	testProxyAt(t, "sctp", proxy, fmt.Sprintf("%s:%d", config.HostIP, config.HostPort), false)
297 300
 }
298 301
 
299
-func TestSCTP6Proxy(t *testing.T) {
300
-	t.Skip("Need to start CI docker with --ipv6")
301
-	skip.If(t, runtime.GOOS == "windows", "sctp is not supported on windows")
302
-
302
+func TestSCTP6ProxyNoListener(t *testing.T) {
303 303
 	backend := NewEchoServer(t, "sctp", "[::1]:0", EchoServerOptions{})
304 304
 	defer backend.Close()
305 305
 	backend.Run()
306
-	frontendAddr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: net.IPv6loopback}}, Port: 0}
307
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
308
-	if err != nil {
309
-		t.Fatal(err)
306
+	backendAddr := backend.LocalAddr().(*sctp.SCTPAddr)
307
+	config := ProxyConfig{
308
+		Proto:         "sctp",
309
+		HostIP:        net.IPv6loopback,
310
+		HostPort:      hopefullyFreePort,
311
+		ContainerIP:   backendAddr.IPAddrs[0].IP,
312
+		ContainerPort: backendAddr.Port,
310 313
 	}
311
-	testProxy(t, "sctp", proxy, false)
314
+	proxy, err := newProxy(config)
315
+	assert.NilError(t, err)
316
+	testProxyAt(t, "sctp", proxy, fmt.Sprintf("[%s]:%d", config.HostIP, config.HostPort), false)
312 317
 }
... ...
@@ -2,11 +2,7 @@
2 2
 // and UDP.
3 3
 package main
4 4
 
5
-import (
6
-	"net"
7
-
8
-	"github.com/ishidawataru/sctp"
9
-)
5
+import "net"
10 6
 
11 7
 // ipVersion refers to IP version - v4 or v6
12 8
 type ipVersion string
... ...
@@ -34,17 +30,3 @@ type Proxy interface {
34 34
 	// BackendAddr returns the proxied address.
35 35
 	BackendAddr() net.Addr
36 36
 }
37
-
38
-// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr.
39
-func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
40
-	switch frontendAddr.(type) {
41
-	case *net.UDPAddr:
42
-		return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
43
-	case *net.TCPAddr:
44
-		return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
45
-	case *sctp.SCTPAddr:
46
-		return NewSCTPProxy(frontendAddr.(*sctp.SCTPAddr), backendAddr.(*sctp.SCTPAddr))
47
-	default:
48
-		panic("Unsupported protocol")
49
-	}
50
-}
... ...
@@ -18,18 +18,7 @@ type SCTPProxy struct {
18 18
 }
19 19
 
20 20
 // NewSCTPProxy creates a new SCTPProxy.
21
-func NewSCTPProxy(frontendAddr, backendAddr *sctp.SCTPAddr) (*SCTPProxy, error) {
22
-	// detect version of hostIP to bind only to correct version
23
-	ipVersion := ipv4
24
-	if frontendAddr.IPAddrs[0].IP.To4() == nil {
25
-		ipVersion = ipv6
26
-	}
27
-	listener, err := sctp.ListenSCTP("sctp"+string(ipVersion), frontendAddr)
28
-	if err != nil {
29
-		return nil, err
30
-	}
31
-	// If the port in frontendAddr was 0 then ListenSCTP will have a picked
32
-	// a port to listen on, hence the call to Addr to get that actual port:
21
+func NewSCTPProxy(listener *sctp.SCTPListener, backendAddr *sctp.SCTPAddr) (*SCTPProxy, error) {
33 22
 	return &SCTPProxy{
34 23
 		listener:     listener,
35 24
 		frontendAddr: listener.Addr().(*sctp.SCTPAddr),
... ...
@@ -16,18 +16,7 @@ type TCPProxy struct {
16 16
 }
17 17
 
18 18
 // NewTCPProxy creates a new TCPProxy.
19
-func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
20
-	// detect version of hostIP to bind only to correct version
21
-	ipVersion := ipv4
22
-	if frontendAddr.IP.To4() == nil {
23
-		ipVersion = ipv6
24
-	}
25
-	listener, err := net.ListenTCP("tcp"+string(ipVersion), frontendAddr)
26
-	if err != nil {
27
-		return nil, err
28
-	}
29
-	// If the port in frontendAddr was 0 then ListenTCP will have a picked
30
-	// a port to listen on, hence the call to Addr to get that actual port:
19
+func NewTCPProxy(listener *net.TCPListener, backendAddr *net.TCPAddr) (*TCPProxy, error) {
31 20
 	return &TCPProxy{
32 21
 		listener:     listener,
33 22
 		frontendAddr: listener.Addr().(*net.TCPAddr),
... ...
@@ -54,16 +54,7 @@ type UDPProxy struct {
54 54
 }
55 55
 
56 56
 // NewUDPProxy creates a new UDPProxy.
57
-func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
58
-	// detect version of hostIP to bind only to correct version
59
-	ipVersion := ipv4
60
-	if frontendAddr.IP.To4() == nil {
61
-		ipVersion = ipv6
62
-	}
63
-	listener, err := net.ListenUDP("udp"+string(ipVersion), frontendAddr)
64
-	if err != nil {
65
-		return nil, err
66
-	}
57
+func NewUDPProxy(listener *net.UDPConn, backendAddr *net.UDPAddr) (*UDPProxy, error) {
67 58
 	return &UDPProxy{
68 59
 		listener:       listener,
69 60
 		frontendAddr:   listener.LocalAddr().(*net.UDPAddr),