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 }