github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/libnetwork/drivers/bridge/port_mapping.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package bridge
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"net"
    11  	"sync"
    12  
    13  	"github.com/docker/docker/libnetwork/types"
    14  	"github.com/ishidawataru/sctp"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
    19  	if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
    20  		return nil, nil
    21  	}
    22  
    23  	defHostIP := net.IPv4zero // 0.0.0.0
    24  	if reqDefBindIP != nil {
    25  		defHostIP = reqDefBindIP
    26  	}
    27  
    28  	var containerIPv6 net.IP
    29  	if ep.addrv6 != nil {
    30  		containerIPv6 = ep.addrv6.IP
    31  	}
    32  
    33  	pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, containerIPv6, defHostIP, ulPxyEnabled)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	return pb, nil
    38  }
    39  
    40  func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIPv4, containerIPv6, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
    41  	bs := make([]types.PortBinding, 0, len(bindings))
    42  	for _, c := range bindings {
    43  		bIPv4 := c.GetCopy()
    44  		bIPv6 := c.GetCopy()
    45  		// Allocate IPv4 Port mappings
    46  		if ok := n.validatePortBindingIPv4(&bIPv4, containerIPv4, defHostIP); ok {
    47  			if err := n.allocatePort(&bIPv4, ulPxyEnabled); err != nil {
    48  				// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
    49  				if cuErr := n.releasePortsInternal(bs); cuErr != nil {
    50  					logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv4 port bindings: %v", bIPv4, cuErr)
    51  				}
    52  				return nil, err
    53  			}
    54  			bs = append(bs, bIPv4)
    55  		}
    56  
    57  		// skip adding implicit v6 addr, when the kernel was booted with `ipv6.disable=1`
    58  		// https://github.com/moby/moby/issues/42288
    59  		isV6Binding := c.HostIP != nil && c.HostIP.To4() == nil
    60  		if !isV6Binding && !IsV6Listenable() {
    61  			continue
    62  		}
    63  
    64  		// Allocate IPv6 Port mappings
    65  		// If the container has no IPv6 address, allow proxying host IPv6 traffic to it
    66  		// by setting up the binding with the IPv4 interface if the userland proxy is enabled
    67  		// This change was added to keep backward compatibility
    68  		containerIP := containerIPv6
    69  		if ulPxyEnabled && (containerIPv6 == nil) {
    70  			containerIP = containerIPv4
    71  		}
    72  		if ok := n.validatePortBindingIPv6(&bIPv6, containerIP, defHostIP); ok {
    73  			if err := n.allocatePort(&bIPv6, ulPxyEnabled); err != nil {
    74  				// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
    75  				if cuErr := n.releasePortsInternal(bs); cuErr != nil {
    76  					logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv6 port bindings: %v", bIPv6, cuErr)
    77  				}
    78  				return nil, err
    79  			}
    80  			bs = append(bs, bIPv6)
    81  		}
    82  	}
    83  	return bs, nil
    84  }
    85  
    86  // validatePortBindingIPv4 validates the port binding, populates the missing Host IP field and returns true
    87  // if this is a valid IPv4 binding, else returns false
    88  func (n *bridgeNetwork) validatePortBindingIPv4(bnd *types.PortBinding, containerIPv4, defHostIP net.IP) bool {
    89  	//Return early if there is a valid Host IP, but its not a IPv4 address
    90  	if len(bnd.HostIP) > 0 && bnd.HostIP.To4() == nil {
    91  		return false
    92  	}
    93  	// Adjust the host address in the operational binding
    94  	if len(bnd.HostIP) == 0 {
    95  		// Return early if the default binding address is an IPv6 address
    96  		if defHostIP.To4() == nil {
    97  			return false
    98  		}
    99  		bnd.HostIP = defHostIP
   100  	}
   101  	bnd.IP = containerIPv4
   102  	return true
   103  
   104  }
   105  
   106  // validatePortBindingIPv6 validates the port binding, populates the missing Host IP field and returns true
   107  // if this is a valid IPv6 binding, else returns false
   108  func (n *bridgeNetwork) validatePortBindingIPv6(bnd *types.PortBinding, containerIP, defHostIP net.IP) bool {
   109  	// Return early if there is no container endpoint
   110  	if containerIP == nil {
   111  		return false
   112  	}
   113  	// Return early if there is a valid Host IP, which is a IPv4 address
   114  	if len(bnd.HostIP) > 0 && bnd.HostIP.To4() != nil {
   115  		return false
   116  	}
   117  
   118  	// Setup a binding to  "::" if Host IP is empty and the default binding IP is 0.0.0.0
   119  	if len(bnd.HostIP) == 0 {
   120  		if defHostIP.Equal(net.IPv4zero) {
   121  			bnd.HostIP = net.IPv6zero
   122  			// If the default binding IP is an IPv6 address, use it
   123  		} else if defHostIP.To4() == nil {
   124  			bnd.HostIP = defHostIP
   125  			// Return false if default binding ip is an IPv4 address
   126  		} else {
   127  			return false
   128  		}
   129  	}
   130  	bnd.IP = containerIP
   131  	return true
   132  }
   133  
   134  func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, ulPxyEnabled bool) error {
   135  	var (
   136  		host net.Addr
   137  		err  error
   138  	)
   139  
   140  	// Adjust HostPortEnd if this is not a range.
   141  	if bnd.HostPortEnd == 0 {
   142  		bnd.HostPortEnd = bnd.HostPort
   143  	}
   144  
   145  	// Construct the container side transport address
   146  	container, err := bnd.ContainerAddr()
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	portmapper := n.portMapper
   152  
   153  	if bnd.HostIP.To4() == nil {
   154  		portmapper = n.portMapperV6
   155  	}
   156  
   157  	// Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
   158  	for i := 0; i < maxAllocatePortAttempts; i++ {
   159  		if host, err = portmapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil {
   160  			break
   161  		}
   162  		// There is no point in immediately retrying to map an explicitly chosen port.
   163  		if bnd.HostPort != 0 {
   164  			logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
   165  			break
   166  		}
   167  		logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
   168  	}
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	// Save the host port (regardless it was or not specified in the binding)
   174  	switch netAddr := host.(type) {
   175  	case *net.TCPAddr:
   176  		bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
   177  		return nil
   178  	case *net.UDPAddr:
   179  		bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
   180  		return nil
   181  	case *sctp.SCTPAddr:
   182  		bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port)
   183  		return nil
   184  	default:
   185  		// For completeness
   186  		return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
   187  	}
   188  }
   189  
   190  func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error {
   191  	return n.releasePortsInternal(ep.portMapping)
   192  }
   193  
   194  func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error {
   195  	var errorBuf bytes.Buffer
   196  
   197  	// Attempt to release all port bindings, do not stop on failure
   198  	for _, m := range bindings {
   199  		if err := n.releasePort(m); err != nil {
   200  			errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
   201  		}
   202  	}
   203  
   204  	if errorBuf.Len() != 0 {
   205  		return errors.New(errorBuf.String())
   206  	}
   207  	return nil
   208  }
   209  
   210  func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
   211  	// Construct the host side transport address
   212  	host, err := bnd.HostAddr()
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	portmapper := n.portMapper
   218  
   219  	if bnd.HostIP.To4() == nil {
   220  		portmapper = n.portMapperV6
   221  	}
   222  
   223  	return portmapper.Unmap(host)
   224  }
   225  
   226  var (
   227  	v6ListenableCached bool
   228  	v6ListenableOnce   sync.Once
   229  )
   230  
   231  // IsV6Listenable returns true when `[::1]:0` is listenable.
   232  // IsV6Listenable returns false mostly when the kernel was booted with `ipv6.disable=1` option.
   233  func IsV6Listenable() bool {
   234  	v6ListenableOnce.Do(func() {
   235  		ln, err := net.Listen("tcp6", "[::1]:0")
   236  		if err != nil {
   237  			// When the kernel was booted with `ipv6.disable=1`,
   238  			// we get err "listen tcp6 [::1]:0: socket: address family not supported by protocol"
   239  			// https://github.com/moby/moby/issues/42288
   240  			logrus.Debugf("port_mapping: v6Listenable=false (%v)", err)
   241  		} else {
   242  			v6ListenableCached = true
   243  			ln.Close()
   244  		}
   245  	})
   246  	return v6ListenableCached
   247  }