Browse code

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

Rather than re-execing docker as the proxy, create a new command docker-proxy
that is much smaller to save memory in the case where there are a lot of
procies being created. Also allows the proxy to be replaced, for example
in Docker for Mac we have a proxy that proxies to osx instead of locally.

This is the vendoring pull for https://github.com/docker/docker/pull/23312

Signed-off-by: Justin Cormack <justin.cormack@docker.com>

Justin Cormack authored on 2016/06/09 20:31:24
Showing 10 changed files
... ...
@@ -7,6 +7,7 @@ docker = docker run --rm -it ${dockerargs} $$EXTRA_ARGS ${container_env} ${build
7 7
 ciargs = -e CIRCLECI -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER=-incontainer=true"
8 8
 cidocker = docker run ${dockerargs} ${ciargs} $$EXTRA_ARGS ${container_env} ${build_image}
9 9
 CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64
10
+export PATH := $(CURDIR)/bin:$(PATH)
10 11
 
11 12
 all: ${build_image}.created build check integration-tests clean
12 13
 
... ...
@@ -24,10 +25,11 @@ build: ${build_image}.created
24 24
 build-local:
25 25
 	@mkdir -p "bin"
26 26
 	$(shell which godep) go build -tags experimental -o "bin/dnet" ./cmd/dnet
27
+	$(shell which godep) go build -o "bin/docker-proxy" ./cmd/proxy
27 28
 
28 29
 clean:
29 30
 	@if [ -d bin ]; then \
30
-		echo "Removing dnet binaries"; \
31
+		echo "Removing dnet and proxy binaries"; \
31 32
 		rm -rf bin; \
32 33
 	fi
33 34
 
... ...
@@ -41,6 +43,7 @@ cross: ${build_image}.created
41 41
 
42 42
 cross-local:
43 43
 	$(shell which godep) go build -o "bin/dnet-$$GOOS-$$GOARCH" ./cmd/dnet
44
+	$(shell which godep) go build -o "bin/docker-proxy-$$GOOS-$$GOARCH" ./cmd/proxy
44 45
 
45 46
 check: ${build_image}.created
46 47
 	@${docker} ./wrapmake.sh check-local
... ...
@@ -102,4 +105,4 @@ circle-ci-check: ${build_image}.created
102 102
 circle-ci-build: ${build_image}.created
103 103
 	@${cidocker} make build-local
104 104
 
105
-circle-ci: circle-ci-check circle-ci-cross circle-ci-build integration-tests
105
+circle-ci: circle-ci-build circle-ci-check circle-ci-cross integration-tests
106 106
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package main
1
+
2
+import (
3
+	"flag"
4
+	"fmt"
5
+	"log"
6
+	"net"
7
+	"os"
8
+	"os/signal"
9
+	"syscall"
10
+)
11
+
12
+func main() {
13
+	f := os.NewFile(3, "signal-parent")
14
+	host, container := parseHostContainerAddrs()
15
+
16
+	p, err := NewProxy(host, container)
17
+	if err != nil {
18
+		fmt.Fprintf(f, "1\n%s", err)
19
+		f.Close()
20
+		os.Exit(1)
21
+	}
22
+	go handleStopSignals(p)
23
+	fmt.Fprint(f, "0\n")
24
+	f.Close()
25
+
26
+	// Run will block until the proxy stops
27
+	p.Run()
28
+}
29
+
30
+// parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP
31
+// net.Addrs to map the host and container ports
32
+func parseHostContainerAddrs() (host net.Addr, container net.Addr) {
33
+	var (
34
+		proto         = flag.String("proto", "tcp", "proxy protocol")
35
+		hostIP        = flag.String("host-ip", "", "host ip")
36
+		hostPort      = flag.Int("host-port", -1, "host port")
37
+		containerIP   = flag.String("container-ip", "", "container ip")
38
+		containerPort = flag.Int("container-port", -1, "container port")
39
+	)
40
+
41
+	flag.Parse()
42
+
43
+	switch *proto {
44
+	case "tcp":
45
+		host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
46
+		container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
47
+	case "udp":
48
+		host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
49
+		container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
50
+	default:
51
+		log.Fatalf("unsupported protocol %s", *proto)
52
+	}
53
+
54
+	return host, container
55
+}
56
+
57
+func handleStopSignals(p Proxy) {
58
+	s := make(chan os.Signal, 10)
59
+	signal.Notify(s, os.Interrupt, syscall.SIGTERM)
60
+
61
+	for range s {
62
+		p.Close()
63
+
64
+		os.Exit(0)
65
+	}
66
+}
0 67
new file mode 100644
... ...
@@ -0,0 +1,219 @@
0
+package main
1
+
2
+import (
3
+	"bytes"
4
+	"flag"
5
+	"fmt"
6
+	"io"
7
+	"net"
8
+	"strings"
9
+	"testing"
10
+	"time"
11
+)
12
+
13
+var _ = flag.Bool("incontainer", false, "Indicates if the test is running in a container")
14
+
15
+var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")
16
+var testBufSize = len(testBuf)
17
+
18
+type EchoServer interface {
19
+	Run()
20
+	Close()
21
+	LocalAddr() net.Addr
22
+}
23
+
24
+type TCPEchoServer struct {
25
+	listener net.Listener
26
+	testCtx  *testing.T
27
+}
28
+
29
+type UDPEchoServer struct {
30
+	conn    net.PacketConn
31
+	testCtx *testing.T
32
+}
33
+
34
+func NewEchoServer(t *testing.T, proto, address string) EchoServer {
35
+	var server EchoServer
36
+	if strings.HasPrefix(proto, "tcp") {
37
+		listener, err := net.Listen(proto, address)
38
+		if err != nil {
39
+			t.Fatal(err)
40
+		}
41
+		server = &TCPEchoServer{listener: listener, testCtx: t}
42
+	} else {
43
+		socket, err := net.ListenPacket(proto, address)
44
+		if err != nil {
45
+			t.Fatal(err)
46
+		}
47
+		server = &UDPEchoServer{conn: socket, testCtx: t}
48
+	}
49
+	return server
50
+}
51
+
52
+func (server *TCPEchoServer) Run() {
53
+	go func() {
54
+		for {
55
+			client, err := server.listener.Accept()
56
+			if err != nil {
57
+				return
58
+			}
59
+			go func(client net.Conn) {
60
+				if _, err := io.Copy(client, client); err != nil {
61
+					server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
62
+				}
63
+				client.Close()
64
+			}(client)
65
+		}
66
+	}()
67
+}
68
+
69
+func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() }
70
+func (server *TCPEchoServer) Close()              { server.listener.Close() }
71
+
72
+func (server *UDPEchoServer) Run() {
73
+	go func() {
74
+		readBuf := make([]byte, 1024)
75
+		for {
76
+			read, from, err := server.conn.ReadFrom(readBuf)
77
+			if err != nil {
78
+				return
79
+			}
80
+			for i := 0; i != read; {
81
+				written, err := server.conn.WriteTo(readBuf[i:read], from)
82
+				if err != nil {
83
+					break
84
+				}
85
+				i += written
86
+			}
87
+		}
88
+	}()
89
+}
90
+
91
+func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() }
92
+func (server *UDPEchoServer) Close()              { server.conn.Close() }
93
+
94
+func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) {
95
+	defer proxy.Close()
96
+	go proxy.Run()
97
+	client, err := net.Dial(proto, addr)
98
+	if err != nil {
99
+		t.Fatalf("Can't connect to the proxy: %v", err)
100
+	}
101
+	defer client.Close()
102
+	client.SetDeadline(time.Now().Add(10 * time.Second))
103
+	if _, err = client.Write(testBuf); err != nil {
104
+		t.Fatal(err)
105
+	}
106
+	recvBuf := make([]byte, testBufSize)
107
+	if _, err = client.Read(recvBuf); err != nil {
108
+		t.Fatal(err)
109
+	}
110
+	if !bytes.Equal(testBuf, recvBuf) {
111
+		t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
112
+	}
113
+}
114
+
115
+func testProxy(t *testing.T, proto string, proxy Proxy) {
116
+	testProxyAt(t, proto, proxy, proxy.FrontendAddr().String())
117
+}
118
+
119
+func TestTCP4Proxy(t *testing.T) {
120
+	backend := NewEchoServer(t, "tcp", "127.0.0.1:0")
121
+	defer backend.Close()
122
+	backend.Run()
123
+	frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
124
+	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
125
+	if err != nil {
126
+		t.Fatal(err)
127
+	}
128
+	testProxy(t, "tcp", proxy)
129
+}
130
+
131
+func TestTCP6Proxy(t *testing.T) {
132
+	backend := NewEchoServer(t, "tcp", "[::1]:0")
133
+	defer backend.Close()
134
+	backend.Run()
135
+	frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
136
+	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
137
+	if err != nil {
138
+		t.Fatal(err)
139
+	}
140
+	testProxy(t, "tcp", proxy)
141
+}
142
+
143
+func TestTCPDualStackProxy(t *testing.T) {
144
+	// If I understand `godoc -src net favoriteAddrFamily` (used by the
145
+	// net.Listen* functions) correctly this should work, but it doesn't.
146
+	t.Skip("No support for dual stack yet")
147
+	backend := NewEchoServer(t, "tcp", "[::1]:0")
148
+	defer backend.Close()
149
+	backend.Run()
150
+	frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
151
+	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
152
+	if err != nil {
153
+		t.Fatal(err)
154
+	}
155
+	ipv4ProxyAddr := &net.TCPAddr{
156
+		IP:   net.IPv4(127, 0, 0, 1),
157
+		Port: proxy.FrontendAddr().(*net.TCPAddr).Port,
158
+	}
159
+	testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String())
160
+}
161
+
162
+func TestUDP4Proxy(t *testing.T) {
163
+	backend := NewEchoServer(t, "udp", "127.0.0.1:0")
164
+	defer backend.Close()
165
+	backend.Run()
166
+	frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
167
+	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
168
+	if err != nil {
169
+		t.Fatal(err)
170
+	}
171
+	testProxy(t, "udp", proxy)
172
+}
173
+
174
+func TestUDP6Proxy(t *testing.T) {
175
+	backend := NewEchoServer(t, "udp", "[::1]:0")
176
+	defer backend.Close()
177
+	backend.Run()
178
+	frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0}
179
+	proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
180
+	if err != nil {
181
+		t.Fatal(err)
182
+	}
183
+	testProxy(t, "udp", proxy)
184
+}
185
+
186
+func TestUDPWriteError(t *testing.T) {
187
+	frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
188
+	// Hopefully, this port will be free: */
189
+	backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587}
190
+	proxy, err := NewProxy(frontendAddr, backendAddr)
191
+	if err != nil {
192
+		t.Fatal(err)
193
+	}
194
+	defer proxy.Close()
195
+	go proxy.Run()
196
+	client, err := net.Dial("udp", "127.0.0.1:25587")
197
+	if err != nil {
198
+		t.Fatalf("Can't connect to the proxy: %v", err)
199
+	}
200
+	defer client.Close()
201
+	// Make sure the proxy doesn't stop when there is no actual backend:
202
+	client.Write(testBuf)
203
+	client.Write(testBuf)
204
+	backend := NewEchoServer(t, "udp", "127.0.0.1:25587")
205
+	defer backend.Close()
206
+	backend.Run()
207
+	client.SetDeadline(time.Now().Add(10 * time.Second))
208
+	if _, err = client.Write(testBuf); err != nil {
209
+		t.Fatal(err)
210
+	}
211
+	recvBuf := make([]byte, testBufSize)
212
+	if _, err = client.Read(recvBuf); err != nil {
213
+		t.Fatal(err)
214
+	}
215
+	if !bytes.Equal(testBuf, recvBuf) {
216
+		t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
217
+	}
218
+}
0 219
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+// docker-proxy provides a network Proxy interface and implementations for TCP
1
+// and UDP.
2
+package main
3
+
4
+import (
5
+	"fmt"
6
+	"net"
7
+)
8
+
9
+// Proxy defines the behavior of a proxy. It forwards traffic back and forth
10
+// between two endpoints : the frontend and the backend.
11
+// It can be used to do software port-mapping between two addresses.
12
+// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000
13
+// to the backend (container) at 172.17.42.108:4000.
14
+type Proxy interface {
15
+	// Run starts forwarding traffic back and forth between the front
16
+	// and back-end addresses.
17
+	Run()
18
+	// Close stops forwarding traffic and close both ends of the Proxy.
19
+	Close()
20
+	// FrontendAddr returns the address on which the proxy is listening.
21
+	FrontendAddr() net.Addr
22
+	// BackendAddr returns the proxied address.
23
+	BackendAddr() net.Addr
24
+}
25
+
26
+// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr.
27
+func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
28
+	switch frontendAddr.(type) {
29
+	case *net.UDPAddr:
30
+		return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
31
+	case *net.TCPAddr:
32
+		return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
33
+	default:
34
+		panic(fmt.Errorf("Unsupported protocol"))
35
+	}
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package main
1
+
2
+import (
3
+	"net"
4
+)
5
+
6
+// StubProxy is a proxy that is a stub (does nothing).
7
+type StubProxy struct {
8
+	frontendAddr net.Addr
9
+	backendAddr  net.Addr
10
+}
11
+
12
+// Run does nothing.
13
+func (p *StubProxy) Run() {}
14
+
15
+// Close does nothing.
16
+func (p *StubProxy) Close() {}
17
+
18
+// FrontendAddr returns the frontend address.
19
+func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr }
20
+
21
+// BackendAddr returns the backend address.
22
+func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr }
23
+
24
+// NewStubProxy creates a new StubProxy
25
+func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
26
+	return &StubProxy{
27
+		frontendAddr: frontendAddr,
28
+		backendAddr:  backendAddr,
29
+	}, nil
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,96 @@
0
+package main
1
+
2
+import (
3
+	"io"
4
+	"net"
5
+	"sync"
6
+	"syscall"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+)
10
+
11
+// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to
12
+// handle TCP traffic forwarding between the frontend and backend addresses.
13
+type TCPProxy struct {
14
+	listener     *net.TCPListener
15
+	frontendAddr *net.TCPAddr
16
+	backendAddr  *net.TCPAddr
17
+}
18
+
19
+// NewTCPProxy creates a new TCPProxy.
20
+func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
21
+	listener, err := net.ListenTCP("tcp", frontendAddr)
22
+	if err != nil {
23
+		return nil, err
24
+	}
25
+	// If the port in frontendAddr was 0 then ListenTCP will have a picked
26
+	// a port to listen on, hence the call to Addr to get that actual port:
27
+	return &TCPProxy{
28
+		listener:     listener,
29
+		frontendAddr: listener.Addr().(*net.TCPAddr),
30
+		backendAddr:  backendAddr,
31
+	}, nil
32
+}
33
+
34
+func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
35
+	backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
36
+	if err != nil {
37
+		logrus.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err)
38
+		client.Close()
39
+		return
40
+	}
41
+
42
+	var wg sync.WaitGroup
43
+	var broker = func(to, from *net.TCPConn) {
44
+		if _, err := io.Copy(to, from); err != nil {
45
+			// If the socket we are writing to is shutdown with
46
+			// SHUT_WR, forward it to the other end of the pipe:
47
+			if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
48
+				from.CloseWrite()
49
+			}
50
+		}
51
+		to.CloseRead()
52
+		wg.Done()
53
+	}
54
+
55
+	wg.Add(2)
56
+	go broker(client, backend)
57
+	go broker(backend, client)
58
+
59
+	finish := make(chan struct{})
60
+	go func() {
61
+		wg.Wait()
62
+		close(finish)
63
+	}()
64
+
65
+	select {
66
+	case <-quit:
67
+	case <-finish:
68
+	}
69
+	client.Close()
70
+	backend.Close()
71
+	<-finish
72
+}
73
+
74
+// Run starts forwarding the traffic using TCP.
75
+func (proxy *TCPProxy) Run() {
76
+	quit := make(chan bool)
77
+	defer close(quit)
78
+	for {
79
+		client, err := proxy.listener.Accept()
80
+		if err != nil {
81
+			logrus.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
82
+			return
83
+		}
84
+		go proxy.clientLoop(client.(*net.TCPConn), quit)
85
+	}
86
+}
87
+
88
+// Close stops forwarding the traffic.
89
+func (proxy *TCPProxy) Close() { proxy.listener.Close() }
90
+
91
+// FrontendAddr returns the TCP address on which the proxy is listening.
92
+func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
93
+
94
+// BackendAddr returns the TCP proxied address.
95
+func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
0 96
new file mode 100644
... ...
@@ -0,0 +1,169 @@
0
+package main
1
+
2
+import (
3
+	"encoding/binary"
4
+	"net"
5
+	"strings"
6
+	"sync"
7
+	"syscall"
8
+	"time"
9
+
10
+	"github.com/Sirupsen/logrus"
11
+)
12
+
13
+const (
14
+	// UDPConnTrackTimeout is the timeout used for UDP connection tracking
15
+	UDPConnTrackTimeout = 90 * time.Second
16
+	// UDPBufSize is the buffer size for the UDP proxy
17
+	UDPBufSize = 65507
18
+)
19
+
20
+// A net.Addr where the IP is split into two fields so you can use it as a key
21
+// in a map:
22
+type connTrackKey struct {
23
+	IPHigh uint64
24
+	IPLow  uint64
25
+	Port   int
26
+}
27
+
28
+func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
29
+	if len(addr.IP) == net.IPv4len {
30
+		return &connTrackKey{
31
+			IPHigh: 0,
32
+			IPLow:  uint64(binary.BigEndian.Uint32(addr.IP)),
33
+			Port:   addr.Port,
34
+		}
35
+	}
36
+	return &connTrackKey{
37
+		IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
38
+		IPLow:  binary.BigEndian.Uint64(addr.IP[8:]),
39
+		Port:   addr.Port,
40
+	}
41
+}
42
+
43
+type connTrackMap map[connTrackKey]*net.UDPConn
44
+
45
+// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
46
+// interface to handle UDP traffic forwarding between the frontend and backend
47
+// addresses.
48
+type UDPProxy struct {
49
+	listener       *net.UDPConn
50
+	frontendAddr   *net.UDPAddr
51
+	backendAddr    *net.UDPAddr
52
+	connTrackTable connTrackMap
53
+	connTrackLock  sync.Mutex
54
+}
55
+
56
+// NewUDPProxy creates a new UDPProxy.
57
+func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
58
+	listener, err := net.ListenUDP("udp", frontendAddr)
59
+	if err != nil {
60
+		return nil, err
61
+	}
62
+	return &UDPProxy{
63
+		listener:       listener,
64
+		frontendAddr:   listener.LocalAddr().(*net.UDPAddr),
65
+		backendAddr:    backendAddr,
66
+		connTrackTable: make(connTrackMap),
67
+	}, nil
68
+}
69
+
70
+func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
71
+	defer func() {
72
+		proxy.connTrackLock.Lock()
73
+		delete(proxy.connTrackTable, *clientKey)
74
+		proxy.connTrackLock.Unlock()
75
+		proxyConn.Close()
76
+	}()
77
+
78
+	readBuf := make([]byte, UDPBufSize)
79
+	for {
80
+		proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
81
+	again:
82
+		read, err := proxyConn.Read(readBuf)
83
+		if err != nil {
84
+			if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
85
+				// This will happen if the last write failed
86
+				// (e.g: nothing is actually listening on the
87
+				// proxied port on the container), ignore it
88
+				// and continue until UDPConnTrackTimeout
89
+				// expires:
90
+				goto again
91
+			}
92
+			return
93
+		}
94
+		for i := 0; i != read; {
95
+			written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr)
96
+			if err != nil {
97
+				return
98
+			}
99
+			i += written
100
+		}
101
+	}
102
+}
103
+
104
+// Run starts forwarding the traffic using UDP.
105
+func (proxy *UDPProxy) Run() {
106
+	readBuf := make([]byte, UDPBufSize)
107
+	for {
108
+		read, from, err := proxy.listener.ReadFromUDP(readBuf)
109
+		if err != nil {
110
+			// NOTE: Apparently ReadFrom doesn't return
111
+			// ECONNREFUSED like Read do (see comment in
112
+			// UDPProxy.replyLoop)
113
+			if !isClosedError(err) {
114
+				logrus.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
115
+			}
116
+			break
117
+		}
118
+
119
+		fromKey := newConnTrackKey(from)
120
+		proxy.connTrackLock.Lock()
121
+		proxyConn, hit := proxy.connTrackTable[*fromKey]
122
+		if !hit {
123
+			proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
124
+			if err != nil {
125
+				logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
126
+				proxy.connTrackLock.Unlock()
127
+				continue
128
+			}
129
+			proxy.connTrackTable[*fromKey] = proxyConn
130
+			go proxy.replyLoop(proxyConn, from, fromKey)
131
+		}
132
+		proxy.connTrackLock.Unlock()
133
+		for i := 0; i != read; {
134
+			written, err := proxyConn.Write(readBuf[i:read])
135
+			if err != nil {
136
+				logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
137
+				break
138
+			}
139
+			i += written
140
+		}
141
+	}
142
+}
143
+
144
+// Close stops forwarding the traffic.
145
+func (proxy *UDPProxy) Close() {
146
+	proxy.listener.Close()
147
+	proxy.connTrackLock.Lock()
148
+	defer proxy.connTrackLock.Unlock()
149
+	for _, conn := range proxy.connTrackTable {
150
+		conn.Close()
151
+	}
152
+}
153
+
154
+// FrontendAddr returns the UDP address on which the proxy is listening.
155
+func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
156
+
157
+// BackendAddr returns the proxied UDP address.
158
+func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
159
+
160
+func isClosedError(err error) bool {
161
+	/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
162
+	 * See:
163
+	 * http://golang.org/src/pkg/net/net.go
164
+	 * https://code.google.com/p/go/issues/detail?id=4337
165
+	 * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
166
+	 */
167
+	return strings.HasSuffix(err.Error(), "use of closed network connection")
168
+}
... ...
@@ -90,7 +90,10 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart,
90 90
 		}
91 91
 
92 92
 		if useProxy {
93
-			m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
93
+			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
94
+			if err != nil {
95
+				return nil, err
96
+			}
94 97
 		} else {
95 98
 			m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort)
96 99
 		}
... ...
@@ -107,7 +110,10 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart,
107 107
 		}
108 108
 
109 109
 		if useProxy {
110
-			m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
110
+			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
111
+			if err != nil {
112
+				return nil, err
113
+			}
111 114
 		} else {
112 115
 			m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort)
113 116
 		}
... ...
@@ -2,8 +2,8 @@ package portmapper
2 2
 
3 3
 import "net"
4 4
 
5
-func newMockProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy {
6
-	return &mockProxyCommand{}
5
+func newMockProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) (userlandProxy, error) {
6
+	return &mockProxyCommand{}, nil
7 7
 }
8 8
 
9 9
 type mockProxyCommand struct {
... ...
@@ -1,29 +1,19 @@
1 1
 package portmapper
2 2
 
3 3
 import (
4
-	"flag"
5 4
 	"fmt"
6 5
 	"io"
7 6
 	"io/ioutil"
8
-	"log"
9 7
 	"net"
10 8
 	"os"
11 9
 	"os/exec"
12
-	"os/signal"
13 10
 	"strconv"
14 11
 	"syscall"
15 12
 	"time"
16
-
17
-	"github.com/docker/docker/pkg/proxy"
18
-	"github.com/docker/docker/pkg/reexec"
19 13
 )
20 14
 
21 15
 const userlandProxyCommandName = "docker-proxy"
22 16
 
23
-func init() {
24
-	reexec.Register(userlandProxyCommandName, execProxy)
25
-}
26
-
27 17
 type userlandProxy interface {
28 18
 	Start() error
29 19
 	Stop() error
... ...
@@ -35,66 +25,15 @@ type proxyCommand struct {
35 35
 	cmd *exec.Cmd
36 36
 }
37 37
 
38
-// execProxy is the reexec function that is registered to start the userland proxies
39
-func execProxy() {
40
-	f := os.NewFile(3, "signal-parent")
41
-	host, container := parseHostContainerAddrs()
38
+func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) (userlandProxy, error) {
39
+	cmd, err := exec.LookPath(userlandProxyCommandName)
42 40
 
43
-	p, err := proxy.NewProxy(host, container)
44 41
 	if err != nil {
45
-		fmt.Fprintf(f, "1\n%s", err)
46
-		f.Close()
47
-		os.Exit(1)
48
-	}
49
-	go handleStopSignals(p)
50
-	fmt.Fprint(f, "0\n")
51
-	f.Close()
52
-
53
-	// Run will block until the proxy stops
54
-	p.Run()
55
-}
56
-
57
-// parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP
58
-// net.Addrs to map the host and container ports
59
-func parseHostContainerAddrs() (host net.Addr, container net.Addr) {
60
-	var (
61
-		proto         = flag.String("proto", "tcp", "proxy protocol")
62
-		hostIP        = flag.String("host-ip", "", "host ip")
63
-		hostPort      = flag.Int("host-port", -1, "host port")
64
-		containerIP   = flag.String("container-ip", "", "container ip")
65
-		containerPort = flag.Int("container-port", -1, "container port")
66
-	)
67
-
68
-	flag.Parse()
69
-
70
-	switch *proto {
71
-	case "tcp":
72
-		host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
73
-		container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
74
-	case "udp":
75
-		host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
76
-		container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
77
-	default:
78
-		log.Fatalf("unsupported protocol %s", *proto)
79
-	}
80
-
81
-	return host, container
82
-}
83
-
84
-func handleStopSignals(p proxy.Proxy) {
85
-	s := make(chan os.Signal, 10)
86
-	signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP)
87
-
88
-	for range s {
89
-		p.Close()
90
-
91
-		os.Exit(0)
42
+		return nil, err
92 43
 	}
93
-}
94 44
 
95
-func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy {
96 45
 	args := []string{
97
-		userlandProxyCommandName,
46
+		cmd,
98 47
 		"-proto", proto,
99 48
 		"-host-ip", hostIP.String(),
100 49
 		"-host-port", strconv.Itoa(hostPort),
... ...
@@ -104,13 +43,13 @@ func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.
104 104
 
105 105
 	return &proxyCommand{
106 106
 		cmd: &exec.Cmd{
107
-			Path: reexec.Self(),
107
+			Path: cmd,
108 108
 			Args: args,
109 109
 			SysProcAttr: &syscall.SysProcAttr{
110 110
 				Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
111 111
 			},
112 112
 		},
113
-	}
113
+	}, nil
114 114
 }
115 115
 
116 116
 func (p *proxyCommand) Start() error {