aboutsummaryrefslogtreecommitdiff
path: root/libpod/oci_util.go
blob: e118348fa3df390476de72df64d8dbba842e4c11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package libpod

import (
	"fmt"
	"net"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/containers/common/libnetwork/types"
	"github.com/containers/podman/v4/libpod/define"
	"github.com/sirupsen/logrus"
)

// Timeout before declaring that runtime has failed to kill a given
// container
const killContainerTimeout = 5 * time.Second

// ociError is used to parse the OCI runtime JSON log.  It is not part of the
// OCI runtime specifications, it follows what runc does
type ociError struct {
	Level string `json:"level,omitempty"`
	Time  string `json:"time,omitempty"`
	Msg   string `json:"msg,omitempty"`
}

// Create systemd unit name for cgroup scopes
func createUnitName(prefix string, name string) string {
	return fmt.Sprintf("%s-%s.scope", prefix, name)
}

// Bind ports to keep them closed on the host
func bindPorts(ports []types.PortMapping) ([]*os.File, error) {
	var files []*os.File
	sctpWarning := true
	for _, port := range ports {
		isV6 := net.ParseIP(port.HostIP).To4() == nil
		if port.HostIP == "" {
			isV6 = false
		}
		protocols := strings.Split(port.Protocol, ",")
		for _, protocol := range protocols {
			for i := uint16(0); i < port.Range; i++ {
				f, err := bindPort(protocol, port.HostIP, port.HostPort+i, isV6, &sctpWarning)
				if err != nil {
					return files, err
				}
				if f != nil {
					files = append(files, f)
				}
			}
		}
	}
	return files, nil
}

func bindPort(protocol, hostIP string, port uint16, isV6 bool, sctpWarning *bool) (*os.File, error) {
	var file *os.File
	switch protocol {
	case "udp":
		var (
			addr *net.UDPAddr
			err  error
		)
		if isV6 {
			addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", hostIP, port))
		} else {
			addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", hostIP, port))
		}
		if err != nil {
			return nil, fmt.Errorf("cannot resolve the UDP address: %w", err)
		}

		proto := "udp4"
		if isV6 {
			proto = "udp6"
		}
		server, err := net.ListenUDP(proto, addr)
		if err != nil {
			return nil, fmt.Errorf("cannot listen on the UDP port: %w", err)
		}
		file, err = server.File()
		if err != nil {
			return nil, fmt.Errorf("cannot get file for UDP socket: %w", err)
		}
		// close the listener
		// note that this does not affect the fd, see the godoc for server.File()
		err = server.Close()
		if err != nil {
			logrus.Warnf("Failed to close connection: %v", err)
		}

	case "tcp":
		var (
			addr *net.TCPAddr
			err  error
		)
		if isV6 {
			addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", hostIP, port))
		} else {
			addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", hostIP, port))
		}
		if err != nil {
			return nil, fmt.Errorf("cannot resolve the TCP address: %w", err)
		}

		proto := "tcp4"
		if isV6 {
			proto = "tcp6"
		}
		server, err := net.ListenTCP(proto, addr)
		if err != nil {
			return nil, fmt.Errorf("cannot listen on the TCP port: %w", err)
		}
		file, err = server.File()
		if err != nil {
			return nil, fmt.Errorf("cannot get file for TCP socket: %w", err)
		}
		// close the listener
		// note that this does not affect the fd, see the godoc for server.File()
		err = server.Close()
		if err != nil {
			logrus.Warnf("Failed to close connection: %v", err)
		}

	case "sctp":
		if *sctpWarning {
			logrus.Info("Port reservation for SCTP is not supported")
			*sctpWarning = false
		}
	default:
		return nil, fmt.Errorf("unknown protocol %s", protocol)
	}
	return file, nil
}

func getOCIRuntimeError(name, runtimeMsg string) error {
	includeFullOutput := logrus.GetLevel() == logrus.DebugLevel

	if match := regexp.MustCompile("(?i).*permission denied.*|.*operation not permitted.*").FindString(runtimeMsg); match != "" {
		errStr := match
		if includeFullOutput {
			errStr = runtimeMsg
		}
		return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimePermissionDenied)
	}
	if match := regexp.MustCompile("(?i).*executable file not found in.*|.*no such file or directory.*").FindString(runtimeMsg); match != "" {
		errStr := match
		if includeFullOutput {
			errStr = runtimeMsg
		}
		return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimeNotFound)
	}
	if match := regexp.MustCompile("`/proc/[a-z0-9-].+/attr.*`").FindString(runtimeMsg); match != "" {
		errStr := match
		if includeFullOutput {
			errStr = runtimeMsg
		}
		if strings.HasSuffix(match, "/exec`") {
			return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrSetSecurityAttribute)
		} else if strings.HasSuffix(match, "/current`") {
			return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrGetSecurityAttribute)
		}
		return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrSecurityAttribute)
	}
	return fmt.Errorf("%s: %s: %w", name, strings.Trim(runtimeMsg, "\n"), define.ErrOCIRuntime)
}