From 712385b0b9ab20bad9c1e9abc05a30e7c2aaa5c8 Mon Sep 17 00:00:00 2001 From: Pradip Dhara Date: Wed, 17 Apr 2019 10:54:10 -0700 Subject: [PATCH] Pick a random host port if the user does not specify a host port. For overlay, l2bridge, and l2tunnel, if the user does not specify a host port, windows driver will select a random port for them. This matches linux behavior. For ics and nat networks the windows OS will choose the port. Signed-off-by: Pradip Dhara Signed-off-by: Madhu Venugopal --- .../windows/overlay/ov_endpoint_windows.go | 16 ++- drivers/windows/overlay/ov_network_windows.go | 11 +- drivers/windows/port_mapping.go | 125 ++++++++++++++++++ drivers/windows/windows.go | 47 +++++-- portallocator/portallocator_windows.go | 10 ++ portmapper/mapper.go | 37 +----- portmapper/mapper_linux.go | 46 +++++++ portmapper/mapper_windows.go | 31 +++++ portmapper/proxy_windows.go | 10 ++ 9 files changed, 285 insertions(+), 48 deletions(-) create mode 100644 drivers/windows/port_mapping.go create mode 100644 portallocator/portallocator_windows.go create mode 100644 portmapper/mapper_linux.go create mode 100644 portmapper/mapper_windows.go create mode 100644 portmapper/proxy_windows.go diff --git a/drivers/windows/overlay/ov_endpoint_windows.go b/drivers/windows/overlay/ov_endpoint_windows.go index d1a4d9ec36..cc5679fd25 100644 --- a/drivers/windows/overlay/ov_endpoint_windows.go +++ b/drivers/windows/overlay/ov_endpoint_windows.go @@ -171,7 +171,19 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, return err } - pbPolicy, err := windows.ConvertPortBindings(epConnectivity.PortBindings) + ep.portMapping = epConnectivity.PortBindings + ep.portMapping, err = windows.AllocatePorts(n.portMapper, ep.portMapping, ep.addr.IP) + if err != nil { + return err + } + + defer func() { + if err != nil { + windows.ReleasePorts(n.portMapper, ep.portMapping) + } + }() + + pbPolicy, err := windows.ConvertPortBindings(ep.portMapping) if err != nil { return err } @@ -229,6 +241,8 @@ func (d *driver) DeleteEndpoint(nid, eid string) error { return fmt.Errorf("endpoint id %q not found", eid) } + windows.ReleasePorts(n.portMapper, ep.portMapping) + n.deleteEndpoint(eid) _, err := endpointRequest("DELETE", ep.profileID, "") diff --git a/drivers/windows/overlay/ov_network_windows.go b/drivers/windows/overlay/ov_network_windows.go index 9cc46f8cfe..592cfc663b 100644 --- a/drivers/windows/overlay/ov_network_windows.go +++ b/drivers/windows/overlay/ov_network_windows.go @@ -11,6 +11,7 @@ import ( "github.com/Microsoft/hcsshim" "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/portmapper" "github.com/docker/libnetwork/types" "github.com/sirupsen/logrus" ) @@ -46,6 +47,7 @@ type network struct { initErr error subnets []*subnet secure bool + portMapper *portmapper.PortMapper sync.Mutex } @@ -89,10 +91,11 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d } n := &network{ - id: id, - driver: d, - endpoints: endpointTable{}, - subnets: []*subnet{}, + id: id, + driver: d, + endpoints: endpointTable{}, + subnets: []*subnet{}, + portMapper: portmapper.New(""), } genData, ok := option[netlabel.GenericData].(map[string]string) diff --git a/drivers/windows/port_mapping.go b/drivers/windows/port_mapping.go new file mode 100644 index 0000000000..51791fd111 --- /dev/null +++ b/drivers/windows/port_mapping.go @@ -0,0 +1,125 @@ +// +build windows + +package windows + +import ( + "bytes" + "errors" + "fmt" + "net" + + "github.com/docker/libnetwork/portmapper" + "github.com/docker/libnetwork/types" + "github.com/ishidawataru/sctp" + "github.com/sirupsen/logrus" +) + +const ( + maxAllocatePortAttempts = 10 +) + +// ErrUnsupportedAddressType is returned when the specified address type is not supported. +type ErrUnsupportedAddressType string + +func (uat ErrUnsupportedAddressType) Error() string { + return fmt.Sprintf("unsupported address type: %s", string(uat)) +} + +// AllocatePorts allocates ports specified in bindings from the portMapper +func AllocatePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding, containerIP net.IP) ([]types.PortBinding, error) { + bs := make([]types.PortBinding, 0, len(bindings)) + for _, c := range bindings { + b := c.GetCopy() + if err := allocatePort(portMapper, &b, containerIP); err != nil { + // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message + if cuErr := ReleasePorts(portMapper, bs); cuErr != nil { + logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr) + } + return nil, err + } + bs = append(bs, b) + } + return bs, nil +} + +func allocatePort(portMapper *portmapper.PortMapper, bnd *types.PortBinding, containerIP net.IP) error { + var ( + host net.Addr + err error + ) + + // Store the container interface address in the operational binding + bnd.IP = containerIP + + // Adjust HostPortEnd if this is not a range. + if bnd.HostPortEnd == 0 { + bnd.HostPortEnd = bnd.HostPort + } + + // Construct the container side transport address + container, err := bnd.ContainerAddr() + if err != nil { + return err + } + + // Try up to maxAllocatePortAttempts times to get a port that's not already allocated. + for i := 0; i < maxAllocatePortAttempts; i++ { + if host, err = portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), false); err == nil { + break + } + // There is no point in immediately retrying to map an explicitly chosen port. + if bnd.HostPort != 0 { + logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err) + break + } + logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1) + } + if err != nil { + return err + } + + // Save the host port (regardless it was or not specified in the binding) + switch netAddr := host.(type) { + case *net.TCPAddr: + bnd.HostPort = uint16(host.(*net.TCPAddr).Port) + break + case *net.UDPAddr: + bnd.HostPort = uint16(host.(*net.UDPAddr).Port) + break + case *sctp.SCTPAddr: + bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port) + break + default: + // For completeness + return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr)) + } + //Windows does not support host port ranges. + bnd.HostPortEnd = bnd.HostPort + return nil +} + +// ReleasePorts releases ports specified in bindings from the portMapper +func ReleasePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding) error { + var errorBuf bytes.Buffer + + // Attempt to release all port bindings, do not stop on failure + for _, m := range bindings { + if err := releasePort(portMapper, m); err != nil { + errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err)) + } + } + + if errorBuf.Len() != 0 { + return errors.New(errorBuf.String()) + } + return nil +} + +func releasePort(portMapper *portmapper.PortMapper, bnd types.PortBinding) error { + // Construct the host side transport address + host, err := bnd.HostAddr() + if err != nil { + return err + } + return portMapper.Unmap(host) +} diff --git a/drivers/windows/windows.go b/drivers/windows/windows.go index c1cc61aa35..c8ab047592 100644 --- a/drivers/windows/windows.go +++ b/drivers/windows/windows.go @@ -25,6 +25,7 @@ import ( "github.com/docker/libnetwork/discoverapi" "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/portmapper" "github.com/docker/libnetwork/types" "github.com/sirupsen/logrus" ) @@ -88,11 +89,12 @@ type hnsEndpoint struct { } type hnsNetwork struct { - id string - created bool - config *networkConfiguration - endpoints map[string]*hnsEndpoint // key: endpoint id - driver *driver // The network's driver + id string + created bool + config *networkConfiguration + endpoints map[string]*hnsEndpoint // key: endpoint id + driver *driver // The network's driver + portMapper *portmapper.PortMapper sync.Mutex } @@ -252,10 +254,11 @@ func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (s func (d *driver) createNetwork(config *networkConfiguration) error { network := &hnsNetwork{ - id: config.ID, - endpoints: make(map[string]*hnsEndpoint), - config: config, - driver: d, + id: config.ID, + endpoints: make(map[string]*hnsEndpoint), + config: config, + driver: d, + portMapper: portmapper.New(""), } d.Lock() @@ -610,7 +613,27 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, endpointStruct.MacAddress = strings.Replace(macAddress.String(), ":", "-", -1) } - endpointStruct.Policies, err = ConvertPortBindings(epConnectivity.PortBindings) + portMapping := epConnectivity.PortBindings + + if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" { + ip := net.IPv4(0, 0, 0, 0) + if ifInfo.Address() != nil { + ip = ifInfo.Address().IP + } + + portMapping, err = AllocatePorts(n.portMapper, portMapping, ip) + if err != nil { + return err + } + + defer func() { + if err != nil { + ReleasePorts(n.portMapper, portMapping) + } + }() + } + + endpointStruct.Policies, err = ConvertPortBindings(portMapping) if err != nil { return err } @@ -721,6 +744,10 @@ func (d *driver) DeleteEndpoint(nid, eid string) error { return err } + if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" { + ReleasePorts(n.portMapper, ep.portMapping) + } + n.Lock() delete(n.endpoints, eid) n.Unlock() diff --git a/portallocator/portallocator_windows.go b/portallocator/portallocator_windows.go new file mode 100644 index 0000000000..98cae14f68 --- /dev/null +++ b/portallocator/portallocator_windows.go @@ -0,0 +1,10 @@ +package portallocator + +const ( + StartPortRange = 60000 + EndPortRange = 65000 +) + +func getDynamicPortRange() (start int, end int, err error) { + return StartPortRange, EndPortRange, nil +} diff --git a/portmapper/mapper.go b/portmapper/mapper.go index 7fa37b1fb6..be4157b0d5 100644 --- a/portmapper/mapper.go +++ b/portmapper/mapper.go @@ -4,9 +4,7 @@ import ( "errors" "fmt" "net" - "sync" - "github.com/docker/libnetwork/iptables" "github.com/docker/libnetwork/portallocator" "github.com/ishidawataru/sctp" "github.com/sirupsen/logrus" @@ -32,20 +30,6 @@ var ( ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address") ) -// PortMapper manages the network address translation -type PortMapper struct { - chain *iptables.ChainInfo - bridgeName string - - // udp:ip:port - currentMappings map[string]*mapping - lock sync.Mutex - - proxyPath string - - Allocator *portallocator.PortAllocator -} - // New returns a new instance of PortMapper func New(proxyPath string) *PortMapper { return NewWithPortAllocator(portallocator.Get(), proxyPath) @@ -60,12 +44,6 @@ func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath stri } } -// SetIptablesChain sets the specified chain into portmapper -func (pm *PortMapper) SetIptablesChain(c *iptables.ChainInfo, bridgeName string) { - pm.chain = c - pm.bridgeName = bridgeName -} - // Map maps the specified container transport address to the host's network address and transport port func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) { return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy) @@ -174,7 +152,7 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, containerIP, containerPort := getIPAndPort(m.container) if hostIP.To4() != nil { - if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { + if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { return nil, err } } @@ -183,7 +161,7 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, // need to undo the iptables rules before we return m.userlandProxy.Stop() if hostIP.To4() != nil { - pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) + pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { return err } @@ -222,7 +200,7 @@ func (pm *PortMapper) Unmap(host net.Addr) error { containerIP, containerPort := getIPAndPort(data.container) hostIP, hostPort := getIPAndPort(data.host) - if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { logrus.Errorf("Error on iptables delete: %s", err) } @@ -248,7 +226,7 @@ func (pm *PortMapper) ReMapAll() { for _, data := range pm.currentMappings { containerIP, containerPort := getIPAndPort(data.container) hostIP, hostPort := getIPAndPort(data.host) - if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { logrus.Errorf("Error on iptables add: %s", err) } } @@ -285,10 +263,3 @@ func getIPAndPort(a net.Addr) (net.IP, int) { } return nil, 0 } - -func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { - if pm.chain == nil { - return nil - } - return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort, pm.bridgeName) -} diff --git a/portmapper/mapper_linux.go b/portmapper/mapper_linux.go new file mode 100644 index 0000000000..0e76c546c5 --- /dev/null +++ b/portmapper/mapper_linux.go @@ -0,0 +1,46 @@ +package portmapper + +import ( + "net" + "sync" + + "github.com/docker/libnetwork/iptables" + "github.com/docker/libnetwork/portallocator" +) + +// PortMapper manages the network address translation +type PortMapper struct { + bridgeName string + + // udp:ip:port + currentMappings map[string]*mapping + lock sync.Mutex + + proxyPath string + + Allocator *portallocator.PortAllocator + chain *iptables.ChainInfo +} + +// SetIptablesChain sets the specified chain into portmapper +func (pm *PortMapper) SetIptablesChain(c *iptables.ChainInfo, bridgeName string) { + pm.chain = c + pm.bridgeName = bridgeName +} + +// AppendForwardingTableEntry adds a port mapping to the forwarding table +func (pm *PortMapper) AppendForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + return pm.forward(iptables.Append, proto, sourceIP, sourcePort, containerIP, containerPort) +} + +// DeleteForwardingTableEntry removes a port mapping from the forwarding table +func (pm *PortMapper) DeleteForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + return pm.forward(iptables.Delete, proto, sourceIP, sourcePort, containerIP, containerPort) +} + +func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + if pm.chain == nil { + return nil + } + return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort, pm.bridgeName) +} diff --git a/portmapper/mapper_windows.go b/portmapper/mapper_windows.go new file mode 100644 index 0000000000..89651e5ad0 --- /dev/null +++ b/portmapper/mapper_windows.go @@ -0,0 +1,31 @@ +package portmapper + +import ( + "net" + "sync" + + "github.com/docker/libnetwork/portallocator" +) + +// PortMapper manages the network address translation +type PortMapper struct { + bridgeName string + + // udp:ip:port + currentMappings map[string]*mapping + lock sync.Mutex + + proxyPath string + + Allocator *portallocator.PortAllocator +} + +// AppendForwardingTableEntry adds a port mapping to the forwarding table +func (pm *PortMapper) AppendForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + return nil +} + +// DeleteForwardingTableEntry removes a port mapping from the forwarding table +func (pm *PortMapper) DeleteForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + return nil +} diff --git a/portmapper/proxy_windows.go b/portmapper/proxy_windows.go new file mode 100644 index 0000000000..06a9e2462c --- /dev/null +++ b/portmapper/proxy_windows.go @@ -0,0 +1,10 @@ +package portmapper + +import ( + "errors" + "net" +) + +func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) { + return nil, errors.New("proxy is unsupported on windows") +}