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 }