github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/libnetwork/drivers/bridge/port_mapping_linux.go (about)

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