github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgen/generate/ports.go (about)

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