Browse code

Make the docker proxy a standalone binary not a re-exec

This reduces memory usage with a lot of docker proxy processes.
On Docker for Mac we are currently carrying a patch to replace
the binary as we modify it to forward ports to the Mac rather
than the Linux VM, this allows us to simply replace this binary
in our packaging with one that has a compatible interface. This
patch does not provide an easy way to substitute a binary as
the interface is complex and there are few use cases, but where
needed this can be done.

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
(cherry picked from commit 0682468431867e3382a759402eb92df5877e310b)
Signed-off-by: Tibor Vass <tibor@docker.com>

Justin Cormack authored on 2016/06/04 01:28:14
Showing 14 changed files
... ...
@@ -131,6 +131,9 @@ clean() {
131 131
 		findArgs+=( -path "vendor/src/$import" )
132 132
 	done
133 133
 
134
+	# The docker proxy command is built from libnetwork
135
+	findArgs+=( -or -path vendor/src/github.com/docker/libnetwork/cmd/proxy )
136
+
134 137
 	local IFS=$'\n'
135 138
 	local prune=( $($find vendor -depth -type d -not '(' "${findArgs[@]}" ')') )
136 139
 	unset IFS
... ...
@@ -2,3 +2,4 @@
2 2
 
3 3
 DOCKER_CLIENT_BINARY_NAME='docker'
4 4
 DOCKER_DAEMON_BINARY_NAME='dockerd'
5
+DOCKER_PROXY_BINARY_NAME='docker-proxy'
... ...
@@ -22,6 +22,7 @@ override_dh_auto_install:
22 22
 	mkdir -p debian/docker-engine/usr/bin
23 23
 	cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary-client/docker)" debian/docker-engine/usr/bin/docker
24 24
 	cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary-daemon/dockerd)" debian/docker-engine/usr/bin/dockerd
25
+	cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary-daemon/docker-proxy)" debian/docker-engine/usr/bin/docker-proxy
25 26
 	cp -aT /usr/local/bin/containerd debian/docker-engine/usr/bin/docker-containerd
26 27
 	cp -aT /usr/local/bin/containerd-shim debian/docker-engine/usr/bin/docker-containerd-shim
27 28
 	cp -aT /usr/local/bin/ctr debian/docker-engine/usr/bin/docker-containerd-ctr
... ...
@@ -126,6 +126,7 @@ export DOCKER_GITCOMMIT=%{_gitcommit}
126 126
 install -d $RPM_BUILD_ROOT/%{_bindir}
127 127
 install -p -m 755 bundles/%{_origversion}/dynbinary-client/docker-%{_origversion} $RPM_BUILD_ROOT/%{_bindir}/docker
128 128
 install -p -m 755 bundles/%{_origversion}/dynbinary-daemon/dockerd-%{_origversion} $RPM_BUILD_ROOT/%{_bindir}/dockerd
129
+install -p -m 755 bundles/%{_origversion}/dynbinary-daemon/docker-proxy-%{_origversion} $RPM_BUILD_ROOT/%{_bindir}/docker-proxy
129 130
 
130 131
 # install containerd
131 132
 install -p -m 755 /usr/local/bin/containerd $RPM_BUILD_ROOT/%{_bindir}/docker-containerd
... ...
@@ -9,5 +9,8 @@ set -e
9 9
 	export BINARY_SHORT_NAME="$DOCKER_DAEMON_BINARY_NAME"
10 10
 	export SOURCE_PATH='./cmd/dockerd'
11 11
 	source "${MAKEDIR}/.binary"
12
+	export BINARY_SHORT_NAME="$DOCKER_PROXY_BINARY_NAME"
13
+	export SOURCE_PATH='./vendor/src/github.com/docker/libnetwork/cmd/proxy'
14
+	source "${MAKEDIR}/.binary"
12 15
 	copy_containerd "$DEST" 'hash'
13 16
 )
... ...
@@ -9,4 +9,7 @@ set -e
9 9
 	export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary
10 10
 	export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here
11 11
 	source "${MAKEDIR}/.binary"
12
+	export BINARY_SHORT_NAME='docker-proxy'
13
+	export SOURCE_PATH='./vendor/src/github.com/docker/libnetwork/cmd/proxy'
14
+	source "${MAKEDIR}/.binary"
12 15
 )
... ...
@@ -5,6 +5,9 @@ BINARY_NAME="dockerd-$VERSION"
5 5
 BINARY_EXTENSION="$(binary_extension)"
6 6
 BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
7 7
 
8
+PROXY_NAME="docker-proxy-$VERSION"
9
+PROXY_FULLNAME="$PROXY_NAME$BINARY_EXTENSION"
10
+
8 11
 CLIENTBIN_NAME="docker-$VERSION"
9 12
 CLIENTBIN_FULLNAME="$CLIENTBIN_NAME$BINARY_EXTENSION"
10 13
 
... ...
@@ -29,6 +32,21 @@ go build -compiler=gccgo \
29 29
 echo "Created binary: $DEST/$BINARY_FULLNAME"
30 30
 ln -sf "$BINARY_FULLNAME" "$DEST/dockerd$BINARY_EXTENSION"
31 31
 
32
+go build -compiler=gccgo \
33
+	-o "$DEST/$PROXY_FULLNAME" \
34
+	"${BUILDFLAGS[@]}" \
35
+	-gccgoflags "
36
+		-g
37
+		$EXTLDFLAGS_STATIC
38
+		-Wl,--no-export-dynamic
39
+		-ldl
40
+		-pthread
41
+	" \
42
+	./vendor/src/github.com/docker/libnetwork/cmd/proxy
43
+
44
+echo "Created binary: $DEST/$PROXY_FULLNAME"
45
+ln -sf "$PROXY_FULLNAME" "$DEST/docker-proxy$BINARY_EXTENSION"
46
+
32 47
 copy_containerd "$DEST" "hash"
33 48
 hash_files "$DEST/$BINARY_FULLNAME"
34 49
 
... ...
@@ -7,4 +7,5 @@ rm -rf "$DEST"
7 7
 	DEST="$(dirname $DEST)/binary-daemon"
8 8
 	source "${MAKEDIR}/.binary-setup"
9 9
 	install_binary "${DEST}/${DOCKER_DAEMON_BINARY_NAME}"
10
+	install_binary "${DEST}/${DOCKER_PROXY_BINARY_NAME}"
10 11
 )
... ...
@@ -18,6 +18,7 @@ for d in "$CROSS/"*/*; do
18 18
 
19 19
 	BINARY_NAME="${DOCKER_CLIENT_BINARY_NAME}-$VERSION"
20 20
 	DAEMON_BINARY_NAME="${DOCKER_DAEMON_BINARY_NAME}-$VERSION"
21
+	PROXY_BINARY_NAME="${DOCKER_PROXY_BINARY_NAME}-$VERSION"
21 22
 	BINARY_EXTENSION="$(export GOOS && binary_extension)"
22 23
 	if [ "$GOOS" = 'windows' ]; then
23 24
 		# if windows use a zip, not tgz
... ...
@@ -29,6 +30,7 @@ for d in "$CROSS/"*/*; do
29 29
 	fi
30 30
 	BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
31 31
 	DAEMON_BINARY_FULLNAME="$DAEMON_BINARY_NAME$BINARY_EXTENSION"
32
+	PROXY_BINARY_FULLNAME="$PROXY_BINARY_NAME$BINARY_EXTENSION"
32 33
 	mkdir -p "$DEST/$GOOS/$GOARCH"
33 34
 	TGZ="$DEST/$GOOS/$GOARCH/$BINARY_NAME$BUNDLE_EXTENSION"
34 35
 
... ...
@@ -47,6 +49,9 @@ for d in "$CROSS/"*/*; do
47 47
 	if [ -f "$d/$DAEMON_BINARY_FULLNAME" ]; then
48 48
 		cp -L "$d/$DAEMON_BINARY_FULLNAME" "$TAR_PATH/${DOCKER_DAEMON_BINARY_NAME}${BINARY_EXTENSION}"
49 49
 	fi
50
+	if [ -f "$d/$PROXY_BINARY_FULLNAME" ]; then
51
+		cp -L "$d/$PROXY_BINARY_FULLNAME" "$TAR_PATH/${DOCKER_PROXY_BINARY_NAME}${BINARY_EXTENSION}"
52
+	fi
50 53
 
51 54
 	# copy over all the containerd binaries
52 55
 	copy_containerd $TAR_PATH
53 56
deleted file mode 100644
... ...
@@ -1,216 +0,0 @@
1
-package proxy
2
-
3
-import (
4
-	"bytes"
5
-	"fmt"
6
-	"io"
7
-	"net"
8
-	"strings"
9
-	"testing"
10
-	"time"
11
-)
12
-
13
-var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")
14
-var testBufSize = len(testBuf)
15
-
16
-type EchoServer interface {
17
-	Run()
18
-	Close()
19
-	LocalAddr() net.Addr
20
-}
21
-
22
-type TCPEchoServer struct {
23
-	listener net.Listener
24
-	testCtx  *testing.T
25
-}
26
-
27
-type UDPEchoServer struct {
28
-	conn    net.PacketConn
29
-	testCtx *testing.T
30
-}
31
-
32
-func NewEchoServer(t *testing.T, proto, address string) EchoServer {
33
-	var server EchoServer
34
-	if strings.HasPrefix(proto, "tcp") {
35
-		listener, err := net.Listen(proto, address)
36
-		if err != nil {
37
-			t.Fatal(err)
38
-		}
39
-		server = &TCPEchoServer{listener: listener, testCtx: t}
40
-	} else {
41
-		socket, err := net.ListenPacket(proto, address)
42
-		if err != nil {
43
-			t.Fatal(err)
44
-		}
45
-		server = &UDPEchoServer{conn: socket, testCtx: t}
46
-	}
47
-	return server
48
-}
49
-
50
-func (server *TCPEchoServer) Run() {
51
-	go func() {
52
-		for {
53
-			client, err := server.listener.Accept()
54
-			if err != nil {
55
-				return
56
-			}
57
-			go func(client net.Conn) {
58
-				if _, err := io.Copy(client, client); err != nil {
59
-					server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
60
-				}
61
-				client.Close()
62
-			}(client)
63
-		}
64
-	}()
65
-}
66
-
67
-func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() }
68
-func (server *TCPEchoServer) Close()              { server.listener.Close() }
69
-
70
-func (server *UDPEchoServer) Run() {
71
-	go func() {
72
-		readBuf := make([]byte, 1024)
73
-		for {
74
-			read, from, err := server.conn.ReadFrom(readBuf)
75
-			if err != nil {
76
-				return
77
-			}
78
-			for i := 0; i != read; {
79
-				written, err := server.conn.WriteTo(readBuf[i:read], from)
80
-				if err != nil {
81
-					break
82
-				}
83
-				i += written
84
-			}
85
-		}
86
-	}()
87
-}
88
-
89
-func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() }
90
-func (server *UDPEchoServer) Close()              { server.conn.Close() }
91
-
92
-func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) {
93
-	defer proxy.Close()
94
-	go proxy.Run()
95
-	client, err := net.Dial(proto, addr)
96
-	if err != nil {
97
-		t.Fatalf("Can't connect to the proxy: %v", err)
98
-	}
99
-	defer client.Close()
100
-	client.SetDeadline(time.Now().Add(10 * time.Second))
101
-	if _, err = client.Write(testBuf); err != nil {
102
-		t.Fatal(err)
103
-	}
104
-	recvBuf := make([]byte, testBufSize)
105
-	if _, err = client.Read(recvBuf); err != nil {
106
-		t.Fatal(err)
107
-	}
108
-	if !bytes.Equal(testBuf, recvBuf) {
109
-		t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
110
-	}
111
-}
112
-
113
-func testProxy(t *testing.T, proto string, proxy Proxy) {
114
-	testProxyAt(t, proto, proxy, proxy.FrontendAddr().String())
115
-}
116
-
117
-func TestTCP4Proxy(t *testing.T) {
118
-	backend := NewEchoServer(t, "tcp", "127.0.0.1:0")
119
-	defer backend.Close()
120
-	backend.Run()
121
-	frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
122
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
123
-	if err != nil {
124
-		t.Fatal(err)
125
-	}
126
-	testProxy(t, "tcp", proxy)
127
-}
128
-
129
-func TestTCP6Proxy(t *testing.T) {
130
-	backend := NewEchoServer(t, "tcp", "[::1]:0")
131
-	defer backend.Close()
132
-	backend.Run()
133
-	frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
134
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
135
-	if err != nil {
136
-		t.Fatal(err)
137
-	}
138
-	testProxy(t, "tcp", proxy)
139
-}
140
-
141
-func TestTCPDualStackProxy(t *testing.T) {
142
-	// If I understand `godoc -src net favoriteAddrFamily` (used by the
143
-	// net.Listen* functions) correctly this should work, but it doesn't.
144
-	t.Skip("No support for dual stack yet")
145
-	backend := NewEchoServer(t, "tcp", "[::1]:0")
146
-	defer backend.Close()
147
-	backend.Run()
148
-	frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
149
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
150
-	if err != nil {
151
-		t.Fatal(err)
152
-	}
153
-	ipv4ProxyAddr := &net.TCPAddr{
154
-		IP:   net.IPv4(127, 0, 0, 1),
155
-		Port: proxy.FrontendAddr().(*net.TCPAddr).Port,
156
-	}
157
-	testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String())
158
-}
159
-
160
-func TestUDP4Proxy(t *testing.T) {
161
-	backend := NewEchoServer(t, "udp", "127.0.0.1:0")
162
-	defer backend.Close()
163
-	backend.Run()
164
-	frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
165
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
166
-	if err != nil {
167
-		t.Fatal(err)
168
-	}
169
-	testProxy(t, "udp", proxy)
170
-}
171
-
172
-func TestUDP6Proxy(t *testing.T) {
173
-	backend := NewEchoServer(t, "udp", "[::1]:0")
174
-	defer backend.Close()
175
-	backend.Run()
176
-	frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0}
177
-	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
178
-	if err != nil {
179
-		t.Fatal(err)
180
-	}
181
-	testProxy(t, "udp", proxy)
182
-}
183
-
184
-func TestUDPWriteError(t *testing.T) {
185
-	frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
186
-	// Hopefully, this port will be free: */
187
-	backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587}
188
-	proxy, err := NewProxy(frontendAddr, backendAddr)
189
-	if err != nil {
190
-		t.Fatal(err)
191
-	}
192
-	defer proxy.Close()
193
-	go proxy.Run()
194
-	client, err := net.Dial("udp", "127.0.0.1:25587")
195
-	if err != nil {
196
-		t.Fatalf("Can't connect to the proxy: %v", err)
197
-	}
198
-	defer client.Close()
199
-	// Make sure the proxy doesn't stop when there is no actual backend:
200
-	client.Write(testBuf)
201
-	client.Write(testBuf)
202
-	backend := NewEchoServer(t, "udp", "127.0.0.1:25587")
203
-	defer backend.Close()
204
-	backend.Run()
205
-	client.SetDeadline(time.Now().Add(10 * time.Second))
206
-	if _, err = client.Write(testBuf); err != nil {
207
-		t.Fatal(err)
208
-	}
209
-	recvBuf := make([]byte, testBufSize)
210
-	if _, err = client.Read(recvBuf); err != nil {
211
-		t.Fatal(err)
212
-	}
213
-	if !bytes.Equal(testBuf, recvBuf) {
214
-		t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
215
-	}
216
-}
217 1
deleted file mode 100644
... ...
@@ -1,37 +0,0 @@
1
-// Package proxy provides a network Proxy interface and implementations for TCP
2
-// and UDP.
3
-package proxy
4
-
5
-import (
6
-	"fmt"
7
-	"net"
8
-)
9
-
10
-// Proxy defines the behavior of a proxy. It forwards traffic back and forth
11
-// between two endpoints : the frontend and the backend.
12
-// It can be used to do software port-mapping between two addresses.
13
-// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000
14
-// to the backend (container) at 172.17.42.108:4000.
15
-type Proxy interface {
16
-	// Run starts forwarding traffic back and forth between the front
17
-	// and back-end addresses.
18
-	Run()
19
-	// Close stops forwarding traffic and close both ends of the Proxy.
20
-	Close()
21
-	// FrontendAddr returns the address on which the proxy is listening.
22
-	FrontendAddr() net.Addr
23
-	// BackendAddr returns the proxied address.
24
-	BackendAddr() net.Addr
25
-}
26
-
27
-// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr.
28
-func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
29
-	switch frontendAddr.(type) {
30
-	case *net.UDPAddr:
31
-		return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
32
-	case *net.TCPAddr:
33
-		return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
34
-	default:
35
-		panic(fmt.Errorf("Unsupported protocol"))
36
-	}
37
-}
38 1
deleted file mode 100644
... ...
@@ -1,31 +0,0 @@
1
-package proxy
2
-
3
-import (
4
-	"net"
5
-)
6
-
7
-// StubProxy is a proxy that is a stub (does nothing).
8
-type StubProxy struct {
9
-	frontendAddr net.Addr
10
-	backendAddr  net.Addr
11
-}
12
-
13
-// Run does nothing.
14
-func (p *StubProxy) Run() {}
15
-
16
-// Close does nothing.
17
-func (p *StubProxy) Close() {}
18
-
19
-// FrontendAddr returns the frontend address.
20
-func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr }
21
-
22
-// BackendAddr returns the backend address.
23
-func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr }
24
-
25
-// NewStubProxy creates a new StubProxy
26
-func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
27
-	return &StubProxy{
28
-		frontendAddr: frontendAddr,
29
-		backendAddr:  backendAddr,
30
-	}, nil
31
-}
32 1
deleted file mode 100644
... ...
@@ -1,96 +0,0 @@
1
-package proxy
2
-
3
-import (
4
-	"io"
5
-	"net"
6
-	"sync"
7
-	"syscall"
8
-
9
-	"github.com/Sirupsen/logrus"
10
-)
11
-
12
-// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to
13
-// handle TCP traffic forwarding between the frontend and backend addresses.
14
-type TCPProxy struct {
15
-	listener     *net.TCPListener
16
-	frontendAddr *net.TCPAddr
17
-	backendAddr  *net.TCPAddr
18
-}
19
-
20
-// NewTCPProxy creates a new TCPProxy.
21
-func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
22
-	listener, err := net.ListenTCP("tcp", frontendAddr)
23
-	if err != nil {
24
-		return nil, err
25
-	}
26
-	// If the port in frontendAddr was 0 then ListenTCP will have a picked
27
-	// a port to listen on, hence the call to Addr to get that actual port:
28
-	return &TCPProxy{
29
-		listener:     listener,
30
-		frontendAddr: listener.Addr().(*net.TCPAddr),
31
-		backendAddr:  backendAddr,
32
-	}, nil
33
-}
34
-
35
-func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
36
-	backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
37
-	if err != nil {
38
-		logrus.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err)
39
-		client.Close()
40
-		return
41
-	}
42
-
43
-	var wg sync.WaitGroup
44
-	var broker = func(to, from *net.TCPConn) {
45
-		if _, err := io.Copy(to, from); err != nil {
46
-			// If the socket we are writing to is shutdown with
47
-			// SHUT_WR, forward it to the other end of the pipe:
48
-			if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
49
-				from.CloseWrite()
50
-			}
51
-		}
52
-		to.CloseRead()
53
-		wg.Done()
54
-	}
55
-
56
-	wg.Add(2)
57
-	go broker(client, backend)
58
-	go broker(backend, client)
59
-
60
-	finish := make(chan struct{})
61
-	go func() {
62
-		wg.Wait()
63
-		close(finish)
64
-	}()
65
-
66
-	select {
67
-	case <-quit:
68
-	case <-finish:
69
-	}
70
-	client.Close()
71
-	backend.Close()
72
-	<-finish
73
-}
74
-
75
-// Run starts forwarding the traffic using TCP.
76
-func (proxy *TCPProxy) Run() {
77
-	quit := make(chan bool)
78
-	defer close(quit)
79
-	for {
80
-		client, err := proxy.listener.Accept()
81
-		if err != nil {
82
-			logrus.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
83
-			return
84
-		}
85
-		go proxy.clientLoop(client.(*net.TCPConn), quit)
86
-	}
87
-}
88
-
89
-// Close stops forwarding the traffic.
90
-func (proxy *TCPProxy) Close() { proxy.listener.Close() }
91
-
92
-// FrontendAddr returns the TCP address on which the proxy is listening.
93
-func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
94
-
95
-// BackendAddr returns the TCP proxied address.
96
-func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
97 1
deleted file mode 100644
... ...
@@ -1,169 +0,0 @@
1
-package proxy
2
-
3
-import (
4
-	"encoding/binary"
5
-	"net"
6
-	"strings"
7
-	"sync"
8
-	"syscall"
9
-	"time"
10
-
11
-	"github.com/Sirupsen/logrus"
12
-)
13
-
14
-const (
15
-	// UDPConnTrackTimeout is the timeout used for UDP connection tracking
16
-	UDPConnTrackTimeout = 90 * time.Second
17
-	// UDPBufSize is the buffer size for the UDP proxy
18
-	UDPBufSize = 65507
19
-)
20
-
21
-// A net.Addr where the IP is split into two fields so you can use it as a key
22
-// in a map:
23
-type connTrackKey struct {
24
-	IPHigh uint64
25
-	IPLow  uint64
26
-	Port   int
27
-}
28
-
29
-func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
30
-	if len(addr.IP) == net.IPv4len {
31
-		return &connTrackKey{
32
-			IPHigh: 0,
33
-			IPLow:  uint64(binary.BigEndian.Uint32(addr.IP)),
34
-			Port:   addr.Port,
35
-		}
36
-	}
37
-	return &connTrackKey{
38
-		IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
39
-		IPLow:  binary.BigEndian.Uint64(addr.IP[8:]),
40
-		Port:   addr.Port,
41
-	}
42
-}
43
-
44
-type connTrackMap map[connTrackKey]*net.UDPConn
45
-
46
-// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
47
-// interface to handle UDP traffic forwarding between the frontend and backend
48
-// addresses.
49
-type UDPProxy struct {
50
-	listener       *net.UDPConn
51
-	frontendAddr   *net.UDPAddr
52
-	backendAddr    *net.UDPAddr
53
-	connTrackTable connTrackMap
54
-	connTrackLock  sync.Mutex
55
-}
56
-
57
-// NewUDPProxy creates a new UDPProxy.
58
-func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
59
-	listener, err := net.ListenUDP("udp", frontendAddr)
60
-	if err != nil {
61
-		return nil, err
62
-	}
63
-	return &UDPProxy{
64
-		listener:       listener,
65
-		frontendAddr:   listener.LocalAddr().(*net.UDPAddr),
66
-		backendAddr:    backendAddr,
67
-		connTrackTable: make(connTrackMap),
68
-	}, nil
69
-}
70
-
71
-func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
72
-	defer func() {
73
-		proxy.connTrackLock.Lock()
74
-		delete(proxy.connTrackTable, *clientKey)
75
-		proxy.connTrackLock.Unlock()
76
-		proxyConn.Close()
77
-	}()
78
-
79
-	readBuf := make([]byte, UDPBufSize)
80
-	for {
81
-		proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
82
-	again:
83
-		read, err := proxyConn.Read(readBuf)
84
-		if err != nil {
85
-			if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
86
-				// This will happen if the last write failed
87
-				// (e.g: nothing is actually listening on the
88
-				// proxied port on the container), ignore it
89
-				// and continue until UDPConnTrackTimeout
90
-				// expires:
91
-				goto again
92
-			}
93
-			return
94
-		}
95
-		for i := 0; i != read; {
96
-			written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr)
97
-			if err != nil {
98
-				return
99
-			}
100
-			i += written
101
-		}
102
-	}
103
-}
104
-
105
-// Run starts forwarding the traffic using UDP.
106
-func (proxy *UDPProxy) Run() {
107
-	readBuf := make([]byte, UDPBufSize)
108
-	for {
109
-		read, from, err := proxy.listener.ReadFromUDP(readBuf)
110
-		if err != nil {
111
-			// NOTE: Apparently ReadFrom doesn't return
112
-			// ECONNREFUSED like Read do (see comment in
113
-			// UDPProxy.replyLoop)
114
-			if !isClosedError(err) {
115
-				logrus.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
116
-			}
117
-			break
118
-		}
119
-
120
-		fromKey := newConnTrackKey(from)
121
-		proxy.connTrackLock.Lock()
122
-		proxyConn, hit := proxy.connTrackTable[*fromKey]
123
-		if !hit {
124
-			proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
125
-			if err != nil {
126
-				logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
127
-				proxy.connTrackLock.Unlock()
128
-				continue
129
-			}
130
-			proxy.connTrackTable[*fromKey] = proxyConn
131
-			go proxy.replyLoop(proxyConn, from, fromKey)
132
-		}
133
-		proxy.connTrackLock.Unlock()
134
-		for i := 0; i != read; {
135
-			written, err := proxyConn.Write(readBuf[i:read])
136
-			if err != nil {
137
-				logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
138
-				break
139
-			}
140
-			i += written
141
-		}
142
-	}
143
-}
144
-
145
-// Close stops forwarding the traffic.
146
-func (proxy *UDPProxy) Close() {
147
-	proxy.listener.Close()
148
-	proxy.connTrackLock.Lock()
149
-	defer proxy.connTrackLock.Unlock()
150
-	for _, conn := range proxy.connTrackTable {
151
-		conn.Close()
152
-	}
153
-}
154
-
155
-// FrontendAddr returns the UDP address on which the proxy is listening.
156
-func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
157
-
158
-// BackendAddr returns the proxied UDP address.
159
-func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
160
-
161
-func isClosedError(err error) bool {
162
-	/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
163
-	 * See:
164
-	 * http://golang.org/src/pkg/net/net.go
165
-	 * https://code.google.com/p/go/issues/detail?id=4337
166
-	 * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
167
-	 */
168
-	return strings.HasSuffix(err.Error(), "use of closed network connection")
169
-}