11
"github.com/containers/common/libimage"
12
"github.com/containers/common/libnetwork/types"
13
"github.com/containers/podman/v5/pkg/specgen"
14
"github.com/containers/podman/v5/pkg/specgenutil"
15
"github.com/containers/podman/v5/utils"
16
"github.com/sirupsen/logrus"
17
"golang.org/x/exp/slices"
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 {
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
44
return previousPort, nil
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, fmt.Errorf("conflicting port mappings for host port %d (protocol %s)", port.HostPort, port.Protocol)
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)
58
// joinTwoContainerPortsToRangePortIfPossible will expect two ports with both no host port set,
60
// the previous port one must have a lower or equal containerPort than the current port.
61
func joinTwoContainerPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
62
previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
63
// no previous port just return the current one
64
if previousPort == nil {
67
if previousPort.ContainerPort+previousPort.Range > port.ContainerPort {
68
// calc the new range use the old range and add the difference between the ports
69
newRange := port.ContainerPort - previousPort.ContainerPort + port.Range
70
// if the newRange is greater than the old range use it
71
// this is important otherwise we would could lower the range
72
if newRange > previousPort.Range {
73
previousPort.Range = newRange
75
return previousPort, nil
77
// we could not join the ports so we append the old one to the list
78
// and return the current port as previous port
79
newPort, err := getRandomHostPort(currentHostPorts, *previousPort)
83
addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, &newPort)
87
func addPortToUsedPorts(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool, port *types.PortMapping) {
88
for i := uint16(0); i < port.Range; i++ {
89
h := port.HostPort + i
90
allHostPorts[h] = true
91
currentHostPorts[h] = true
92
c := port.ContainerPort + i
93
allContainerPorts[c] = true
95
*ports = append(*ports, *port)
98
// getRandomHostPort get a random host port mapping for the given port
99
// the caller has to supply an array with the already used ports
100
func getRandomHostPort(hostPorts *[65536]bool, port types.PortMapping) (types.PortMapping, error) {
102
for i := 0; i < 15; i++ {
103
ranPort, err := utils.GetRandomPort()
108
// if port range is exceeds max port we cannot use it
109
if ranPort+int(port.Range) > 65535 {
113
// check if there is a port in the range which is used
114
for j := 0; j < int(port.Range); j++ {
116
if hostPorts[ranPort+j] {
121
port.HostPort = uint16(ranPort)
125
// add range to error message if needed
128
rangePort = fmt.Sprintf("with range %d ", port.Range)
131
return port, fmt.Errorf("failed to find an open port to expose container port %d %son the host", port.ContainerPort, rangePort)
134
// Parse port maps to port mappings.
135
// Returns a set of port mappings, and maps of utilized container and
137
func ParsePortMapping(portMappings []types.PortMapping, exposePorts map[uint16][]string) ([]types.PortMapping, error) {
138
if len(portMappings) == 0 && len(exposePorts) == 0 {
142
// tempMapping stores the ports without ip and protocol
143
type tempMapping struct {
149
// portMap is a temporary structure to sort all ports
150
// the map is hostIp -> protocol -> array of mappings
151
portMap := make(map[string]map[string][]tempMapping)
153
// allUsedContainerPorts stores all used ports for each protocol
154
// the key is the protocol and the array is 65536 elements long for each port.
155
allUsedContainerPortsMap := make(map[string][65536]bool)
156
allUsedHostPortsMap := make(map[string][65536]bool)
158
// First, we need to validate the ports passed in the specgen
159
for _, port := range portMappings {
160
// First, check proto
161
protocols, err := checkProtocol(port.Protocol, true)
165
if port.HostIP != "" {
166
if ip := net.ParseIP(port.HostIP); ip == nil {
167
return nil, fmt.Errorf("invalid IP address %q in port mapping", port.HostIP)
171
// Validate port numbers and range.
172
portRange := port.Range
176
containerPort := port.ContainerPort
177
if containerPort == 0 {
178
return nil, fmt.Errorf("container port number must be non-0")
180
hostPort := port.HostPort
181
if uint32(portRange-1)+uint32(containerPort) > 65535 {
182
return nil, fmt.Errorf("container port range exceeds maximum allowable port number")
184
if uint32(portRange-1)+uint32(hostPort) > 65535 {
185
return nil, fmt.Errorf("host port range exceeds maximum allowable port number")
188
hostProtoMap, ok := portMap[port.HostIP]
190
hostProtoMap = make(map[string][]tempMapping)
191
for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
192
hostProtoMap[proto] = make([]tempMapping, 0)
194
portMap[port.HostIP] = hostProtoMap
198
hostPort: port.HostPort,
199
containerPort: port.ContainerPort,
200
rangePort: portRange,
203
for _, proto := range protocols {
204
hostProtoMap[proto] = append(hostProtoMap[proto], p)
208
// we do no longer need the original port mappings
209
// set it to 0 length so we can reuse it to populate
210
// the slice again while keeping the underlying capacity
211
portMappings = portMappings[:0]
213
for hostIP, protoMap := range portMap {
214
for protocol, ports := range protoMap {
219
// 1. sort the ports by host port
220
// use a small hack to make sure ports with host port 0 are sorted last
221
sort.Slice(ports, func(i, j int) bool {
222
if ports[i].hostPort == ports[j].hostPort {
223
return ports[i].containerPort < ports[j].containerPort
225
if ports[i].hostPort == 0 {
228
if ports[j].hostPort == 0 {
231
return ports[i].hostPort < ports[j].hostPort
234
allUsedContainerPorts := allUsedContainerPortsMap[protocol]
235
allUsedHostPorts := allUsedHostPortsMap[protocol]
236
var usedHostPorts [65536]bool
238
var previousPort *types.PortMapping
240
for i = 0; i < len(ports); i++ {
241
if ports[i].hostPort == 0 {
242
// because the ports are sorted and host port 0 is last
243
// we can break when we hit 0
244
// we will fit them in afterwards
247
p := types.PortMapping{
250
HostPort: ports[i].hostPort,
251
ContainerPort: ports[i].containerPort,
252
Range: ports[i].rangePort,
255
previousPort, err = joinTwoPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
256
&allUsedContainerPorts, &usedHostPorts, previousPort, p)
261
if previousPort != nil {
262
addPortToUsedPorts(&portMappings, &allUsedHostPorts,
263
&allUsedContainerPorts, &usedHostPorts, previousPort)
266
// now take care of the hostPort = 0 ports
269
p := types.PortMapping{
272
ContainerPort: ports[i].containerPort,
273
Range: ports[i].rangePort,
276
previousPort, err = joinTwoContainerPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
277
&allUsedContainerPorts, &usedHostPorts, previousPort, p)
283
if previousPort != nil {
284
newPort, err := getRandomHostPort(&usedHostPorts, *previousPort)
288
addPortToUsedPorts(&portMappings, &allUsedHostPorts,
289
&allUsedContainerPorts, &usedHostPorts, &newPort)
292
allUsedContainerPortsMap[protocol] = allUsedContainerPorts
293
allUsedHostPortsMap[protocol] = allUsedHostPorts
297
if len(exposePorts) > 0 {
298
logrus.Debugf("Adding exposed ports")
300
for port, protocols := range exposePorts {
301
newProtocols := make([]string, 0, len(protocols))
302
for _, protocol := range protocols {
303
if !allUsedContainerPortsMap[protocol][port] {
304
p := types.PortMapping{
309
allPorts := allUsedContainerPortsMap[protocol]
310
p, err := getRandomHostPort(&allPorts, p)
314
portMappings = append(portMappings, p)
315
// Mark this port as used so it doesn't get re-generated
316
allPorts[p.HostPort] = true
318
newProtocols = append(newProtocols, protocol)
321
// make sure to delete the key from the map if there are no protocols left
322
if len(newProtocols) == 0 {
323
delete(exposePorts, port)
325
exposePorts[port] = newProtocols
329
return portMappings, nil
332
func appendProtocolsNoDuplicates(slice []string, protocols []string) []string {
333
for _, proto := range protocols {
334
if slices.Contains(slice, proto) {
337
slice = append(slice, proto)
342
// Make final port mappings for the container
343
func createPortMappings(s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.PortMapping, map[uint16][]string, error) {
344
expose := make(map[uint16]string)
346
if imageData != nil {
347
expose, err = GenExposedPorts(imageData.Config.ExposedPorts)
353
toExpose := make(map[uint16][]string, len(s.Expose)+len(expose))
354
for _, expose := range []map[uint16]string{expose, s.Expose} {
355
for port, proto := range expose {
357
return nil, nil, fmt.Errorf("cannot expose 0 as it is not a valid port number")
359
protocols, err := checkProtocol(proto, false)
361
return nil, nil, fmt.Errorf("validating protocols for exposed port %d: %w", port, err)
363
toExpose[port] = appendProtocolsNoDuplicates(toExpose[port], protocols)
367
publishPorts := toExpose
368
if s.PublishExposedPorts == nil || !*s.PublishExposedPorts {
372
finalMappings, err := ParsePortMapping(s.PortMappings, publishPorts)
376
return finalMappings, toExpose, nil
379
// Check a string to ensure it is a comma-separated set of valid protocols
380
func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
381
protocols := make(map[string]struct{})
382
splitProto := strings.Split(protocol, ",")
383
// Don't error on duplicates - just deduplicate
384
for _, p := range splitProto {
385
p = strings.ToLower(p)
388
protocols[protoTCP] = struct{}{}
390
protocols[protoUDP] = struct{}{}
393
return nil, fmt.Errorf("protocol SCTP is not allowed for exposed ports")
395
protocols[protoSCTP] = struct{}{}
397
return nil, fmt.Errorf("unrecognized protocol %q in port mapping", p)
401
finalProto := []string{}
402
for p := range protocols {
403
finalProto = append(finalProto, p)
406
// This shouldn't be possible, but check anyways
407
if len(finalProto) == 0 {
408
return nil, fmt.Errorf("no valid protocols specified for port mapping")
411
return finalProto, nil
414
func GenExposedPorts(exposedPorts map[string]struct{}) (map[uint16]string, error) {
415
expose := make([]string, 0, len(exposedPorts))
416
for e := range exposedPorts {
417
expose = append(expose, e)
419
toReturn, err := specgenutil.CreateExpose(expose)
421
return nil, fmt.Errorf("unable to convert image EXPOSE: %w", err)