github.com/containers/podman/v4@v4.9.4/pkg/specgen/generate/ports.go (about)

     1  //go:build !remote
     2  // +build !remote
     3  
     4  package generate
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/containers/common/libimage"
    13  	"github.com/containers/common/libnetwork/types"
    14  	"github.com/containers/podman/v4/utils"
    15  
    16  	"github.com/containers/common/pkg/util"
    17  	"github.com/containers/podman/v4/pkg/specgen"
    18  	"github.com/containers/podman/v4/pkg/specgenutil"
    19  	"github.com/sirupsen/logrus"
    20  )
    21  
    22  const (
    23  	protoTCP  = "tcp"
    24  	protoUDP  = "udp"
    25  	protoSCTP = "sctp"
    26  )
    27  
    28  // joinTwoPortsToRangePortIfPossible will expect two ports the previous port one must have a lower or equal hostPort than the current port.
    29  func joinTwoPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
    30  	previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
    31  	// no previous port just return the current one
    32  	if previousPort == nil {
    33  		return &port, nil
    34  	}
    35  	if previousPort.HostPort+previousPort.Range >= port.HostPort {
    36  		// check if the port range matches the host and container ports
    37  		portDiff := port.HostPort - previousPort.HostPort
    38  		if portDiff == port.ContainerPort-previousPort.ContainerPort {
    39  			// calc the new range use the old range and add the difference between the ports
    40  			newRange := port.Range + portDiff
    41  			// if the newRange is greater than the old range use it
    42  			// this is important otherwise we would could lower the range
    43  			if newRange > previousPort.Range {
    44  				previousPort.Range = newRange
    45  			}
    46  			return previousPort, nil
    47  		}
    48  		// if both host port ranges overlap and the container port range did not match
    49  		// we have to error because we cannot assign the same host port to more than one container port
    50  		if previousPort.HostPort+previousPort.Range-1 > port.HostPort {
    51  			return nil, fmt.Errorf("conflicting port mappings for host port %d (protocol %s)", port.HostPort, port.Protocol)
    52  		}
    53  	}
    54  	// we could not join the ports so we append the old one to the list
    55  	// and return the current port as previous port
    56  	addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, previousPort)
    57  	return &port, nil
    58  }
    59  
    60  // joinTwoContainerPortsToRangePortIfPossible will expect two ports with both no host port set,
    61  //
    62  //	the previous port one must have a lower or equal containerPort than the current port.
    63  func joinTwoContainerPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
    64  	previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
    65  	// no previous port just return the current one
    66  	if previousPort == nil {
    67  		return &port, nil
    68  	}
    69  	if previousPort.ContainerPort+previousPort.Range > port.ContainerPort {
    70  		// calc the new range use the old range and add the difference between the ports
    71  		newRange := port.ContainerPort - previousPort.ContainerPort + port.Range
    72  		// if the newRange is greater than the old range use it
    73  		// this is important otherwise we would could lower the range
    74  		if newRange > previousPort.Range {
    75  			previousPort.Range = newRange
    76  		}
    77  		return previousPort, nil
    78  	}
    79  	// we could not join the ports so we append the old one to the list
    80  	// and return the current port as previous port
    81  	newPort, err := getRandomHostPort(currentHostPorts, *previousPort)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, &newPort)
    86  	return &port, nil
    87  }
    88  
    89  func addPortToUsedPorts(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool, port *types.PortMapping) {
    90  	for i := uint16(0); i < port.Range; i++ {
    91  		h := port.HostPort + i
    92  		allHostPorts[h] = true
    93  		currentHostPorts[h] = true
    94  		c := port.ContainerPort + i
    95  		allContainerPorts[c] = true
    96  	}
    97  	*ports = append(*ports, *port)
    98  }
    99  
   100  // getRandomHostPort get a random host port mapping for the given port
   101  // the caller has to supply an array with the already used ports
   102  func getRandomHostPort(hostPorts *[65536]bool, port types.PortMapping) (types.PortMapping, error) {
   103  outer:
   104  	for i := 0; i < 15; i++ {
   105  		ranPort, err := utils.GetRandomPort()
   106  		if err != nil {
   107  			return port, err
   108  		}
   109  
   110  		// if port range is exceeds max port we cannot use it
   111  		if ranPort+int(port.Range) > 65535 {
   112  			continue
   113  		}
   114  
   115  		// check if there is a port in the range which is used
   116  		for j := 0; j < int(port.Range); j++ {
   117  			// port already used
   118  			if hostPorts[ranPort+j] {
   119  				continue outer
   120  			}
   121  		}
   122  
   123  		port.HostPort = uint16(ranPort)
   124  		return port, nil
   125  	}
   126  
   127  	// add range to error message if needed
   128  	rangePort := ""
   129  	if port.Range > 1 {
   130  		rangePort = fmt.Sprintf("with range %d ", port.Range)
   131  	}
   132  
   133  	return port, fmt.Errorf("failed to find an open port to expose container port %d %son the host", port.ContainerPort, rangePort)
   134  }
   135  
   136  // Parse port maps to port mappings.
   137  // Returns a set of port mappings, and maps of utilized container and
   138  // host ports.
   139  func ParsePortMapping(portMappings []types.PortMapping, exposePorts map[uint16][]string) ([]types.PortMapping, error) {
   140  	if len(portMappings) == 0 && len(exposePorts) == 0 {
   141  		return nil, nil
   142  	}
   143  
   144  	// tempMapping stores the ports without ip and protocol
   145  	type tempMapping struct {
   146  		hostPort      uint16
   147  		containerPort uint16
   148  		rangePort     uint16
   149  	}
   150  
   151  	// portMap is a temporary structure to sort all ports
   152  	// the map is hostIp -> protocol -> array of mappings
   153  	portMap := make(map[string]map[string][]tempMapping)
   154  
   155  	// allUsedContainerPorts stores all used ports for each protocol
   156  	// the key is the protocol and the array is 65536 elements long for each port.
   157  	allUsedContainerPortsMap := make(map[string][65536]bool)
   158  	allUsedHostPortsMap := make(map[string][65536]bool)
   159  
   160  	// First, we need to validate the ports passed in the specgen
   161  	for _, port := range portMappings {
   162  		// First, check proto
   163  		protocols, err := checkProtocol(port.Protocol, true)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  		if port.HostIP != "" {
   168  			if ip := net.ParseIP(port.HostIP); ip == nil {
   169  				return nil, fmt.Errorf("invalid IP address %q in port mapping", port.HostIP)
   170  			}
   171  		}
   172  
   173  		// Validate port numbers and range.
   174  		portRange := port.Range
   175  		if portRange == 0 {
   176  			portRange = 1
   177  		}
   178  		containerPort := port.ContainerPort
   179  		if containerPort == 0 {
   180  			return nil, fmt.Errorf("container port number must be non-0")
   181  		}
   182  		hostPort := port.HostPort
   183  		if uint32(portRange-1)+uint32(containerPort) > 65535 {
   184  			return nil, fmt.Errorf("container port range exceeds maximum allowable port number")
   185  		}
   186  		if uint32(portRange-1)+uint32(hostPort) > 65535 {
   187  			return nil, fmt.Errorf("host port range exceeds maximum allowable port number")
   188  		}
   189  
   190  		hostProtoMap, ok := portMap[port.HostIP]
   191  		if !ok {
   192  			hostProtoMap = make(map[string][]tempMapping)
   193  			for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
   194  				hostProtoMap[proto] = make([]tempMapping, 0)
   195  			}
   196  			portMap[port.HostIP] = hostProtoMap
   197  		}
   198  
   199  		p := tempMapping{
   200  			hostPort:      port.HostPort,
   201  			containerPort: port.ContainerPort,
   202  			rangePort:     portRange,
   203  		}
   204  
   205  		for _, proto := range protocols {
   206  			hostProtoMap[proto] = append(hostProtoMap[proto], p)
   207  		}
   208  	}
   209  
   210  	// we do no longer need the original port mappings
   211  	// set it to 0 length so we can reuse it to populate
   212  	// the slice again while keeping the underlying capacity
   213  	portMappings = portMappings[:0]
   214  
   215  	for hostIP, protoMap := range portMap {
   216  		for protocol, ports := range protoMap {
   217  			ports := ports
   218  			if len(ports) == 0 {
   219  				continue
   220  			}
   221  			// 1. sort the ports by host port
   222  			// use a small hack to make sure ports with host port 0 are sorted last
   223  			sort.Slice(ports, func(i, j int) bool {
   224  				if ports[i].hostPort == ports[j].hostPort {
   225  					return ports[i].containerPort < ports[j].containerPort
   226  				}
   227  				if ports[i].hostPort == 0 {
   228  					return false
   229  				}
   230  				if ports[j].hostPort == 0 {
   231  					return true
   232  				}
   233  				return ports[i].hostPort < ports[j].hostPort
   234  			})
   235  
   236  			allUsedContainerPorts := allUsedContainerPortsMap[protocol]
   237  			allUsedHostPorts := allUsedHostPortsMap[protocol]
   238  			var usedHostPorts [65536]bool
   239  
   240  			var previousPort *types.PortMapping
   241  			var i int
   242  			for i = 0; i < len(ports); i++ {
   243  				if ports[i].hostPort == 0 {
   244  					// because the ports are sorted and host port 0 is last
   245  					// we can break when we hit 0
   246  					// we will fit them in afterwards
   247  					break
   248  				}
   249  				p := types.PortMapping{
   250  					HostIP:        hostIP,
   251  					Protocol:      protocol,
   252  					HostPort:      ports[i].hostPort,
   253  					ContainerPort: ports[i].containerPort,
   254  					Range:         ports[i].rangePort,
   255  				}
   256  				var err error
   257  				previousPort, err = joinTwoPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
   258  					&allUsedContainerPorts, &usedHostPorts, previousPort, p)
   259  				if err != nil {
   260  					return nil, err
   261  				}
   262  			}
   263  			if previousPort != nil {
   264  				addPortToUsedPorts(&portMappings, &allUsedHostPorts,
   265  					&allUsedContainerPorts, &usedHostPorts, previousPort)
   266  			}
   267  
   268  			// now take care of the hostPort = 0 ports
   269  			previousPort = nil
   270  			for i < len(ports) {
   271  				p := types.PortMapping{
   272  					HostIP:        hostIP,
   273  					Protocol:      protocol,
   274  					ContainerPort: ports[i].containerPort,
   275  					Range:         ports[i].rangePort,
   276  				}
   277  				var err error
   278  				previousPort, err = joinTwoContainerPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
   279  					&allUsedContainerPorts, &usedHostPorts, previousPort, p)
   280  				if err != nil {
   281  					return nil, err
   282  				}
   283  				i++
   284  			}
   285  			if previousPort != nil {
   286  				newPort, err := getRandomHostPort(&usedHostPorts, *previousPort)
   287  				if err != nil {
   288  					return nil, err
   289  				}
   290  				addPortToUsedPorts(&portMappings, &allUsedHostPorts,
   291  					&allUsedContainerPorts, &usedHostPorts, &newPort)
   292  			}
   293  
   294  			allUsedContainerPortsMap[protocol] = allUsedContainerPorts
   295  			allUsedHostPortsMap[protocol] = allUsedHostPorts
   296  		}
   297  	}
   298  
   299  	if len(exposePorts) > 0 {
   300  		logrus.Debugf("Adding exposed ports")
   301  
   302  		for port, protocols := range exposePorts {
   303  			newProtocols := make([]string, 0, len(protocols))
   304  			for _, protocol := range protocols {
   305  				if !allUsedContainerPortsMap[protocol][port] {
   306  					p := types.PortMapping{
   307  						ContainerPort: port,
   308  						Protocol:      protocol,
   309  						Range:         1,
   310  					}
   311  					allPorts := allUsedContainerPortsMap[protocol]
   312  					p, err := getRandomHostPort(&allPorts, p)
   313  					if err != nil {
   314  						return nil, err
   315  					}
   316  					portMappings = append(portMappings, p)
   317  					// Mark this port as used so it doesn't get re-generated
   318  					allPorts[p.HostPort] = true
   319  				} else {
   320  					newProtocols = append(newProtocols, protocol)
   321  				}
   322  			}
   323  			// make sure to delete the key from the map if there are no protocols left
   324  			if len(newProtocols) == 0 {
   325  				delete(exposePorts, port)
   326  			} else {
   327  				exposePorts[port] = newProtocols
   328  			}
   329  		}
   330  	}
   331  	return portMappings, nil
   332  }
   333  
   334  func appendProtocolsNoDuplicates(slice []string, protocols []string) []string {
   335  	for _, proto := range protocols {
   336  		if util.StringInSlice(proto, slice) {
   337  			continue
   338  		}
   339  		slice = append(slice, proto)
   340  	}
   341  	return slice
   342  }
   343  
   344  // Make final port mappings for the container
   345  func createPortMappings(s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.PortMapping, map[uint16][]string, error) {
   346  	expose := make(map[uint16]string)
   347  	var err error
   348  	if imageData != nil {
   349  		expose, err = GenExposedPorts(imageData.Config.ExposedPorts)
   350  		if err != nil {
   351  			return nil, nil, err
   352  		}
   353  	}
   354  
   355  	toExpose := make(map[uint16][]string, len(s.Expose)+len(expose))
   356  	for _, expose := range []map[uint16]string{expose, s.Expose} {
   357  		for port, proto := range expose {
   358  			if port == 0 {
   359  				return nil, nil, fmt.Errorf("cannot expose 0 as it is not a valid port number")
   360  			}
   361  			protocols, err := checkProtocol(proto, false)
   362  			if err != nil {
   363  				return nil, nil, fmt.Errorf("validating protocols for exposed port %d: %w", port, err)
   364  			}
   365  			toExpose[port] = appendProtocolsNoDuplicates(toExpose[port], protocols)
   366  		}
   367  	}
   368  
   369  	publishPorts := toExpose
   370  	if !s.PublishExposedPorts {
   371  		publishPorts = nil
   372  	}
   373  
   374  	finalMappings, err := ParsePortMapping(s.PortMappings, publishPorts)
   375  	if err != nil {
   376  		return nil, nil, err
   377  	}
   378  	return finalMappings, toExpose, nil
   379  }
   380  
   381  // Check a string to ensure it is a comma-separated set of valid protocols
   382  func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
   383  	protocols := make(map[string]struct{})
   384  	splitProto := strings.Split(protocol, ",")
   385  	// Don't error on duplicates - just deduplicate
   386  	for _, p := range splitProto {
   387  		p = strings.ToLower(p)
   388  		switch p {
   389  		case protoTCP, "":
   390  			protocols[protoTCP] = struct{}{}
   391  		case protoUDP:
   392  			protocols[protoUDP] = struct{}{}
   393  		case protoSCTP:
   394  			if !allowSCTP {
   395  				return nil, fmt.Errorf("protocol SCTP is not allowed for exposed ports")
   396  			}
   397  			protocols[protoSCTP] = struct{}{}
   398  		default:
   399  			return nil, fmt.Errorf("unrecognized protocol %q in port mapping", p)
   400  		}
   401  	}
   402  
   403  	finalProto := []string{}
   404  	for p := range protocols {
   405  		finalProto = append(finalProto, p)
   406  	}
   407  
   408  	// This shouldn't be possible, but check anyways
   409  	if len(finalProto) == 0 {
   410  		return nil, fmt.Errorf("no valid protocols specified for port mapping")
   411  	}
   412  
   413  	return finalProto, nil
   414  }
   415  
   416  func GenExposedPorts(exposedPorts map[string]struct{}) (map[uint16]string, error) {
   417  	expose := make([]string, 0, len(exposedPorts))
   418  	for e := range exposedPorts {
   419  		expose = append(expose, e)
   420  	}
   421  	toReturn, err := specgenutil.CreateExpose(expose)
   422  	if err != nil {
   423  		return nil, fmt.Errorf("unable to convert image EXPOSE: %w", err)
   424  	}
   425  	return toReturn, nil
   426  }