podman

Форк
0
424 строки · 13.1 Кб
1
//go:build !remote
2

3
package generate
4

5
import (
6
	"fmt"
7
	"net"
8
	"sort"
9
	"strings"
10

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"
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, fmt.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
//
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 {
65
		return &port, nil
66
	}
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
74
		}
75
		return previousPort, nil
76
	}
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)
80
	if err != nil {
81
		return nil, err
82
	}
83
	addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, &newPort)
84
	return &port, nil
85
}
86

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
94
	}
95
	*ports = append(*ports, *port)
96
}
97

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) {
101
outer:
102
	for i := 0; i < 15; i++ {
103
		ranPort, err := utils.GetRandomPort()
104
		if err != nil {
105
			return port, err
106
		}
107

108
		// if port range is exceeds max port we cannot use it
109
		if ranPort+int(port.Range) > 65535 {
110
			continue
111
		}
112

113
		// check if there is a port in the range which is used
114
		for j := 0; j < int(port.Range); j++ {
115
			// port already used
116
			if hostPorts[ranPort+j] {
117
				continue outer
118
			}
119
		}
120

121
		port.HostPort = uint16(ranPort)
122
		return port, nil
123
	}
124

125
	// add range to error message if needed
126
	rangePort := ""
127
	if port.Range > 1 {
128
		rangePort = fmt.Sprintf("with range %d ", port.Range)
129
	}
130

131
	return port, fmt.Errorf("failed to find an open port to expose container port %d %son the host", port.ContainerPort, rangePort)
132
}
133

134
// Parse port maps to port mappings.
135
// Returns a set of port mappings, and maps of utilized container and
136
// host ports.
137
func ParsePortMapping(portMappings []types.PortMapping, exposePorts map[uint16][]string) ([]types.PortMapping, error) {
138
	if len(portMappings) == 0 && len(exposePorts) == 0 {
139
		return nil, nil
140
	}
141

142
	// tempMapping stores the ports without ip and protocol
143
	type tempMapping struct {
144
		hostPort      uint16
145
		containerPort uint16
146
		rangePort     uint16
147
	}
148

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)
152

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)
157

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)
162
		if err != nil {
163
			return nil, err
164
		}
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)
168
			}
169
		}
170

171
		// Validate port numbers and range.
172
		portRange := port.Range
173
		if portRange == 0 {
174
			portRange = 1
175
		}
176
		containerPort := port.ContainerPort
177
		if containerPort == 0 {
178
			return nil, fmt.Errorf("container port number must be non-0")
179
		}
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")
183
		}
184
		if uint32(portRange-1)+uint32(hostPort) > 65535 {
185
			return nil, fmt.Errorf("host port range exceeds maximum allowable port number")
186
		}
187

188
		hostProtoMap, ok := portMap[port.HostIP]
189
		if !ok {
190
			hostProtoMap = make(map[string][]tempMapping)
191
			for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
192
				hostProtoMap[proto] = make([]tempMapping, 0)
193
			}
194
			portMap[port.HostIP] = hostProtoMap
195
		}
196

197
		p := tempMapping{
198
			hostPort:      port.HostPort,
199
			containerPort: port.ContainerPort,
200
			rangePort:     portRange,
201
		}
202

203
		for _, proto := range protocols {
204
			hostProtoMap[proto] = append(hostProtoMap[proto], p)
205
		}
206
	}
207

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]
212

213
	for hostIP, protoMap := range portMap {
214
		for protocol, ports := range protoMap {
215
			ports := ports
216
			if len(ports) == 0 {
217
				continue
218
			}
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
224
				}
225
				if ports[i].hostPort == 0 {
226
					return false
227
				}
228
				if ports[j].hostPort == 0 {
229
					return true
230
				}
231
				return ports[i].hostPort < ports[j].hostPort
232
			})
233

234
			allUsedContainerPorts := allUsedContainerPortsMap[protocol]
235
			allUsedHostPorts := allUsedHostPortsMap[protocol]
236
			var usedHostPorts [65536]bool
237

238
			var previousPort *types.PortMapping
239
			var i int
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
245
					break
246
				}
247
				p := types.PortMapping{
248
					HostIP:        hostIP,
249
					Protocol:      protocol,
250
					HostPort:      ports[i].hostPort,
251
					ContainerPort: ports[i].containerPort,
252
					Range:         ports[i].rangePort,
253
				}
254
				var err error
255
				previousPort, err = joinTwoPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
256
					&allUsedContainerPorts, &usedHostPorts, previousPort, p)
257
				if err != nil {
258
					return nil, err
259
				}
260
			}
261
			if previousPort != nil {
262
				addPortToUsedPorts(&portMappings, &allUsedHostPorts,
263
					&allUsedContainerPorts, &usedHostPorts, previousPort)
264
			}
265

266
			// now take care of the hostPort = 0 ports
267
			previousPort = nil
268
			for i < len(ports) {
269
				p := types.PortMapping{
270
					HostIP:        hostIP,
271
					Protocol:      protocol,
272
					ContainerPort: ports[i].containerPort,
273
					Range:         ports[i].rangePort,
274
				}
275
				var err error
276
				previousPort, err = joinTwoContainerPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
277
					&allUsedContainerPorts, &usedHostPorts, previousPort, p)
278
				if err != nil {
279
					return nil, err
280
				}
281
				i++
282
			}
283
			if previousPort != nil {
284
				newPort, err := getRandomHostPort(&usedHostPorts, *previousPort)
285
				if err != nil {
286
					return nil, err
287
				}
288
				addPortToUsedPorts(&portMappings, &allUsedHostPorts,
289
					&allUsedContainerPorts, &usedHostPorts, &newPort)
290
			}
291

292
			allUsedContainerPortsMap[protocol] = allUsedContainerPorts
293
			allUsedHostPortsMap[protocol] = allUsedHostPorts
294
		}
295
	}
296

297
	if len(exposePorts) > 0 {
298
		logrus.Debugf("Adding exposed ports")
299

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{
305
						ContainerPort: port,
306
						Protocol:      protocol,
307
						Range:         1,
308
					}
309
					allPorts := allUsedContainerPortsMap[protocol]
310
					p, err := getRandomHostPort(&allPorts, p)
311
					if err != nil {
312
						return nil, err
313
					}
314
					portMappings = append(portMappings, p)
315
					// Mark this port as used so it doesn't get re-generated
316
					allPorts[p.HostPort] = true
317
				} else {
318
					newProtocols = append(newProtocols, protocol)
319
				}
320
			}
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)
324
			} else {
325
				exposePorts[port] = newProtocols
326
			}
327
		}
328
	}
329
	return portMappings, nil
330
}
331

332
func appendProtocolsNoDuplicates(slice []string, protocols []string) []string {
333
	for _, proto := range protocols {
334
		if slices.Contains(slice, proto) {
335
			continue
336
		}
337
		slice = append(slice, proto)
338
	}
339
	return slice
340
}
341

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)
345
	var err error
346
	if imageData != nil {
347
		expose, err = GenExposedPorts(imageData.Config.ExposedPorts)
348
		if err != nil {
349
			return nil, nil, err
350
		}
351
	}
352

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 {
356
			if port == 0 {
357
				return nil, nil, fmt.Errorf("cannot expose 0 as it is not a valid port number")
358
			}
359
			protocols, err := checkProtocol(proto, false)
360
			if err != nil {
361
				return nil, nil, fmt.Errorf("validating protocols for exposed port %d: %w", port, err)
362
			}
363
			toExpose[port] = appendProtocolsNoDuplicates(toExpose[port], protocols)
364
		}
365
	}
366

367
	publishPorts := toExpose
368
	if s.PublishExposedPorts == nil || !*s.PublishExposedPorts {
369
		publishPorts = nil
370
	}
371

372
	finalMappings, err := ParsePortMapping(s.PortMappings, publishPorts)
373
	if err != nil {
374
		return nil, nil, err
375
	}
376
	return finalMappings, toExpose, nil
377
}
378

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)
386
		switch p {
387
		case protoTCP, "":
388
			protocols[protoTCP] = struct{}{}
389
		case protoUDP:
390
			protocols[protoUDP] = struct{}{}
391
		case protoSCTP:
392
			if !allowSCTP {
393
				return nil, fmt.Errorf("protocol SCTP is not allowed for exposed ports")
394
			}
395
			protocols[protoSCTP] = struct{}{}
396
		default:
397
			return nil, fmt.Errorf("unrecognized protocol %q in port mapping", p)
398
		}
399
	}
400

401
	finalProto := []string{}
402
	for p := range protocols {
403
		finalProto = append(finalProto, p)
404
	}
405

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")
409
	}
410

411
	return finalProto, nil
412
}
413

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)
418
	}
419
	toReturn, err := specgenutil.CreateExpose(expose)
420
	if err != nil {
421
		return nil, fmt.Errorf("unable to convert image EXPOSE: %w", err)
422
	}
423
	return toReturn, nil
424
}
425

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.