mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
686be57d0a
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
247 lines
7.3 KiB
Go
247 lines
7.3 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package bridge
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
|
|
"github.com/docker/docker/libnetwork/types"
|
|
"github.com/ishidawataru/sctp"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
|
|
if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
defHostIP := net.IPv4zero // 0.0.0.0
|
|
if reqDefBindIP != nil {
|
|
defHostIP = reqDefBindIP
|
|
}
|
|
|
|
var containerIPv6 net.IP
|
|
if ep.addrv6 != nil {
|
|
containerIPv6 = ep.addrv6.IP
|
|
}
|
|
|
|
pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, containerIPv6, defHostIP, ulPxyEnabled)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pb, nil
|
|
}
|
|
|
|
func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIPv4, containerIPv6, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
|
|
bs := make([]types.PortBinding, 0, len(bindings))
|
|
for _, c := range bindings {
|
|
bIPv4 := c.GetCopy()
|
|
bIPv6 := c.GetCopy()
|
|
// Allocate IPv4 Port mappings
|
|
if ok := n.validatePortBindingIPv4(&bIPv4, containerIPv4, defHostIP); ok {
|
|
if err := n.allocatePort(&bIPv4, ulPxyEnabled); err != nil {
|
|
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
|
|
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
|
|
logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv4 port bindings: %v", bIPv4, cuErr)
|
|
}
|
|
return nil, err
|
|
}
|
|
bs = append(bs, bIPv4)
|
|
}
|
|
|
|
// skip adding implicit v6 addr, when the kernel was booted with `ipv6.disable=1`
|
|
// https://github.com/moby/moby/issues/42288
|
|
isV6Binding := c.HostIP != nil && c.HostIP.To4() == nil
|
|
if !isV6Binding && !IsV6Listenable() {
|
|
continue
|
|
}
|
|
|
|
// Allocate IPv6 Port mappings
|
|
// If the container has no IPv6 address, allow proxying host IPv6 traffic to it
|
|
// by setting up the binding with the IPv4 interface if the userland proxy is enabled
|
|
// This change was added to keep backward compatibility
|
|
containerIP := containerIPv6
|
|
if ulPxyEnabled && (containerIPv6 == nil) {
|
|
containerIP = containerIPv4
|
|
}
|
|
if ok := n.validatePortBindingIPv6(&bIPv6, containerIP, defHostIP); ok {
|
|
if err := n.allocatePort(&bIPv6, ulPxyEnabled); err != nil {
|
|
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
|
|
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
|
|
logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv6 port bindings: %v", bIPv6, cuErr)
|
|
}
|
|
return nil, err
|
|
}
|
|
bs = append(bs, bIPv6)
|
|
}
|
|
}
|
|
return bs, nil
|
|
}
|
|
|
|
// validatePortBindingIPv4 validates the port binding, populates the missing Host IP field and returns true
|
|
// if this is a valid IPv4 binding, else returns false
|
|
func (n *bridgeNetwork) validatePortBindingIPv4(bnd *types.PortBinding, containerIPv4, defHostIP net.IP) bool {
|
|
//Return early if there is a valid Host IP, but its not a IPv4 address
|
|
if len(bnd.HostIP) > 0 && bnd.HostIP.To4() == nil {
|
|
return false
|
|
}
|
|
// Adjust the host address in the operational binding
|
|
if len(bnd.HostIP) == 0 {
|
|
// Return early if the default binding address is an IPv6 address
|
|
if defHostIP.To4() == nil {
|
|
return false
|
|
}
|
|
bnd.HostIP = defHostIP
|
|
}
|
|
bnd.IP = containerIPv4
|
|
return true
|
|
|
|
}
|
|
|
|
// validatePortBindingIPv6 validates the port binding, populates the missing Host IP field and returns true
|
|
// if this is a valid IPv6 binding, else returns false
|
|
func (n *bridgeNetwork) validatePortBindingIPv6(bnd *types.PortBinding, containerIP, defHostIP net.IP) bool {
|
|
// Return early if there is no container endpoint
|
|
if containerIP == nil {
|
|
return false
|
|
}
|
|
// Return early if there is a valid Host IP, which is a IPv4 address
|
|
if len(bnd.HostIP) > 0 && bnd.HostIP.To4() != nil {
|
|
return false
|
|
}
|
|
|
|
// Setup a binding to "::" if Host IP is empty and the default binding IP is 0.0.0.0
|
|
if len(bnd.HostIP) == 0 {
|
|
if defHostIP.Equal(net.IPv4zero) {
|
|
bnd.HostIP = net.IPv6zero
|
|
// If the default binding IP is an IPv6 address, use it
|
|
} else if defHostIP.To4() == nil {
|
|
bnd.HostIP = defHostIP
|
|
// Return false if default binding ip is an IPv4 address
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
bnd.IP = containerIP
|
|
return true
|
|
}
|
|
|
|
func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, ulPxyEnabled bool) error {
|
|
var (
|
|
host net.Addr
|
|
err error
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
portmapper := n.portMapper
|
|
|
|
if bnd.HostIP.To4() == nil {
|
|
portmapper = n.portMapperV6
|
|
}
|
|
|
|
// 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), ulPxyEnabled); 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)
|
|
return nil
|
|
case *net.UDPAddr:
|
|
bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
|
|
return nil
|
|
case *sctp.SCTPAddr:
|
|
bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port)
|
|
return nil
|
|
default:
|
|
// For completeness
|
|
return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
|
|
}
|
|
}
|
|
|
|
func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error {
|
|
return n.releasePortsInternal(ep.portMapping)
|
|
}
|
|
|
|
func (n *bridgeNetwork) releasePortsInternal(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 := n.releasePort(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 (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
|
|
// Construct the host side transport address
|
|
host, err := bnd.HostAddr()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
portmapper := n.portMapper
|
|
|
|
if bnd.HostIP.To4() == nil {
|
|
portmapper = n.portMapperV6
|
|
}
|
|
|
|
return portmapper.Unmap(host)
|
|
}
|
|
|
|
var (
|
|
v6ListenableCached bool
|
|
v6ListenableOnce sync.Once
|
|
)
|
|
|
|
// IsV6Listenable returns true when `[::1]:0` is listenable.
|
|
// IsV6Listenable returns false mostly when the kernel was booted with `ipv6.disable=1` option.
|
|
func IsV6Listenable() bool {
|
|
v6ListenableOnce.Do(func() {
|
|
ln, err := net.Listen("tcp6", "[::1]:0")
|
|
if err != nil {
|
|
// When the kernel was booted with `ipv6.disable=1`,
|
|
// we get err "listen tcp6 [::1]:0: socket: address family not supported by protocol"
|
|
// https://github.com/moby/moby/issues/42288
|
|
logrus.Debugf("port_mapping: v6Listenable=false (%v)", err)
|
|
} else {
|
|
v6ListenableCached = true
|
|
ln.Close()
|
|
}
|
|
})
|
|
return v6ListenableCached
|
|
}
|