Browse Source

Merge pull request #195 from LK4D4/dummyproxy

Add dummy proxy on port map
Madhu Venugopal 10 years ago
parent
commit
c14334bfc7

+ 9 - 9
libnetwork/portmapper/mapper.go

@@ -84,6 +84,8 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, usePr
 
 
 		if useProxy {
 		if useProxy {
 			m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
 			m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
+		} else {
+			m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort)
 		}
 		}
 	case *net.UDPAddr:
 	case *net.UDPAddr:
 		proto = "udp"
 		proto = "udp"
@@ -99,6 +101,8 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, usePr
 
 
 		if useProxy {
 		if useProxy {
 			m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
 			m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
+		} else {
+			m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort)
 		}
 		}
 	default:
 	default:
 		return nil, ErrUnknownBackendAddressType
 		return nil, ErrUnknownBackendAddressType
@@ -123,9 +127,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, usePr
 
 
 	cleanup := func() error {
 	cleanup := func() error {
 		// need to undo the iptables rules before we return
 		// need to undo the iptables rules before we return
-		if m.userlandProxy != nil {
-			m.userlandProxy.Stop()
-		}
+		m.userlandProxy.Stop()
 		pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
 		pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
 		if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
 		if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
 			return err
 			return err
@@ -134,13 +136,11 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, usePr
 		return nil
 		return nil
 	}
 	}
 
 
-	if m.userlandProxy != nil {
-		if err := m.userlandProxy.Start(); err != nil {
-			if err := cleanup(); err != nil {
-				return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
-			}
-			return nil, err
+	if err := m.userlandProxy.Start(); err != nil {
+		if err := cleanup(); err != nil {
+			return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
 		}
 		}
+		return nil, err
 	}
 	}
 
 
 	pm.currentMappings[key] = m
 	pm.currentMappings[key] = m

+ 75 - 0
libnetwork/portmapper/mapper_test.go

@@ -2,6 +2,7 @@ package portmapper
 
 
 import (
 import (
 	"net"
 	"net"
+	"strings"
 	"testing"
 	"testing"
 
 
 	"github.com/docker/libnetwork/iptables"
 	"github.com/docker/libnetwork/iptables"
@@ -194,3 +195,77 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
 		hosts = []net.Addr{}
 		hosts = []net.Addr{}
 	}
 	}
 }
 }
+
+func TestMapTCPDummyListen(t *testing.T) {
+	pm := New()
+	dstIP := net.ParseIP("0.0.0.0")
+	dstAddr := &net.TCPAddr{IP: dstIP, Port: 80}
+
+	// no-op for dummy
+	srcAddr := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+
+	addrEqual := func(addr1, addr2 net.Addr) bool {
+		return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+	}
+
+	if host, err := pm.Map(srcAddr, dstIP, 80, false); err != nil {
+		t.Fatalf("Failed to allocate port: %s", err)
+	} else if !addrEqual(dstAddr, host) {
+		t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+			dstAddr.String(), dstAddr.Network(), host.String(), host.Network())
+	}
+	if _, err := net.Listen("tcp", "0.0.0.0:80"); err == nil {
+		t.Fatal("Listen on mapped port without proxy should fail")
+	} else {
+		if !strings.Contains(err.Error(), "address already in use") {
+			t.Fatalf("Error should be about address already in use, got %v", err)
+		}
+	}
+	if _, err := net.Listen("tcp", "0.0.0.0:81"); err != nil {
+		t.Fatal(err)
+	}
+	if host, err := pm.Map(srcAddr, dstIP, 81, false); err == nil {
+		t.Fatalf("Bound port shouldn't be allocated, but it was on: %v", host)
+	} else {
+		if !strings.Contains(err.Error(), "address already in use") {
+			t.Fatalf("Error should be about address already in use, got %v", err)
+		}
+	}
+}
+
+func TestMapUDPDummyListen(t *testing.T) {
+	pm := New()
+	dstIP := net.ParseIP("0.0.0.0")
+	dstAddr := &net.UDPAddr{IP: dstIP, Port: 80}
+
+	// no-op for dummy
+	srcAddr := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+
+	addrEqual := func(addr1, addr2 net.Addr) bool {
+		return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+	}
+
+	if host, err := pm.Map(srcAddr, dstIP, 80, false); err != nil {
+		t.Fatalf("Failed to allocate port: %s", err)
+	} else if !addrEqual(dstAddr, host) {
+		t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+			dstAddr.String(), dstAddr.Network(), host.String(), host.Network())
+	}
+	if _, err := net.ListenUDP("udp", &net.UDPAddr{IP: dstIP, Port: 80}); err == nil {
+		t.Fatal("Listen on mapped port without proxy should fail")
+	} else {
+		if !strings.Contains(err.Error(), "address already in use") {
+			t.Fatalf("Error should be about address already in use, got %v", err)
+		}
+	}
+	if _, err := net.ListenUDP("udp", &net.UDPAddr{IP: dstIP, Port: 81}); err != nil {
+		t.Fatal(err)
+	}
+	if host, err := pm.Map(srcAddr, dstIP, 81, false); err == nil {
+		t.Fatalf("Bound port shouldn't be allocated, but it was on: %v", host)
+	} else {
+		if !strings.Contains(err.Error(), "address already in use") {
+			t.Fatalf("Error should be about address already in use, got %v", err)
+		}
+	}
+}

+ 48 - 0
libnetwork/portmapper/proxy.go

@@ -3,6 +3,7 @@ package portmapper
 import (
 import (
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"log"
 	"log"
 	"net"
 	"net"
@@ -159,3 +160,50 @@ func (p *proxyCommand) Stop() error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+// dummyProxy just listen on some port, it is needed to prevent accidental
+// port allocations on bound port, because without userland proxy we using
+// iptables rules and not net.Listen
+type dummyProxy struct {
+	listener io.Closer
+	addr     net.Addr
+}
+
+func newDummyProxy(proto string, hostIP net.IP, hostPort int) userlandProxy {
+	switch proto {
+	case "tcp":
+		addr := &net.TCPAddr{IP: hostIP, Port: hostPort}
+		return &dummyProxy{addr: addr}
+	case "udp":
+		addr := &net.UDPAddr{IP: hostIP, Port: hostPort}
+		return &dummyProxy{addr: addr}
+	}
+	return nil
+}
+
+func (p *dummyProxy) Start() error {
+	switch addr := p.addr.(type) {
+	case *net.TCPAddr:
+		l, err := net.ListenTCP("tcp", addr)
+		if err != nil {
+			return err
+		}
+		p.listener = l
+	case *net.UDPAddr:
+		l, err := net.ListenUDP("udp", addr)
+		if err != nil {
+			return err
+		}
+		p.listener = l
+	default:
+		return fmt.Errorf("Unknown addr type: %T", p.addr)
+	}
+	return nil
+}
+
+func (p *dummyProxy) Stop() error {
+	if p.listener != nil {
+		return p.listener.Close()
+	}
+	return nil
+}