mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-28 11:17:59 +00:00
Replaces regexp.MustCompile with regexp.Delayed from go.podman.io/storage/pkg/regexp for global regular expressions. This avoids compiling regular expressions during the global init scope, improving startup performance for all commands. Tests are kept using regexp.MustCompile where applicable. Fixes: #29510 Signed-off-by: Vishnu Kothakapu <vishnukothakapu27@gmail.com>
199 lines
6.2 KiB
Go
199 lines
6.2 KiB
Go
//go:build !remote && (linux || freebsd)
|
|
|
|
package libpod
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.podman.io/storage/pkg/regexp"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"go.podman.io/common/libnetwork/types"
|
|
"go.podman.io/podman/v6/libpod/define"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// Bind ports to keep them closed on the host
|
|
func bindPorts(ports []types.PortMapping, forceListen bool) ([]*os.File, error) {
|
|
var files []*os.File
|
|
sctpWarning := true
|
|
for _, port := range ports {
|
|
isV6 := false
|
|
if port.HostIP != "" {
|
|
isV6 = net.ParseIP(port.HostIP).To4() == nil
|
|
}
|
|
protocols := strings.SplitSeq(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, forceListen)
|
|
if err != nil {
|
|
// close all open ports in case of early error so we do not
|
|
// rely on the garbage collector to close them
|
|
for _, f := range files {
|
|
f.Close()
|
|
}
|
|
return nil, err
|
|
}
|
|
if f != nil {
|
|
files = append(files, f)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
// bindPort reserves a port on the host using socket+bind, listen() only when listen is set to true
|
|
// Dual-stack bind by default unless hostIP is specified.
|
|
func bindPort(protocol, hostIP string, port uint16, isV6 bool, sctpWarning *bool, forceListen bool) (*os.File, error) {
|
|
switch protocol {
|
|
case "tcp", "udp":
|
|
sockType := unix.SOCK_STREAM
|
|
if protocol == "udp" {
|
|
sockType = unix.SOCK_DGRAM
|
|
}
|
|
|
|
domain, sa, err := buildSockAddr(hostIP, port, isV6)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fd, err := unix.Socket(domain, sockType|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
// If hostIP == "" and IPv6 is not supported, fall back to IPv4
|
|
if hostIP == "" && errors.Is(err, unix.EAFNOSUPPORT) {
|
|
return bindPortV4Fallback(protocol, sockType, port)
|
|
}
|
|
return nil, fmt.Errorf("cannot create socket for %s port %d: %w", protocol, port, err)
|
|
}
|
|
|
|
if err := setupSocketOpts(fd, domain, hostIP); err != nil {
|
|
unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
|
|
if err := unix.Bind(fd, sa); err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("cannot bind %s port %s: %w", protocol, net.JoinHostPort(hostIP, strconv.FormatUint(uint64(port), 10)), err)
|
|
}
|
|
|
|
if forceListen && sockType == unix.SOCK_STREAM {
|
|
if err := unix.Listen(fd, 0); err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("cannot listen on %s port %s: %w", protocol, net.JoinHostPort(hostIP, strconv.FormatUint(uint64(port), 10)), err)
|
|
}
|
|
}
|
|
|
|
return os.NewFile(uintptr(fd), fmt.Sprintf("reservation-%s-%d", protocol, port)), nil
|
|
|
|
case "sctp":
|
|
if *sctpWarning {
|
|
logrus.Info("Port reservation for SCTP is not supported")
|
|
*sctpWarning = false
|
|
}
|
|
return nil, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown protocol %s", protocol)
|
|
}
|
|
}
|
|
|
|
func buildSockAddr(hostIP string, port uint16, isV6 bool) (int, unix.Sockaddr, error) {
|
|
// default behaviour when hostIP == "" is to bind dual-stack
|
|
// if hostIP != ""; determine the stack using the address specified
|
|
if hostIP == "" {
|
|
return unix.AF_INET6, &unix.SockaddrInet6{Port: int(port)}, nil
|
|
}
|
|
ip := net.ParseIP(hostIP)
|
|
if ip == nil {
|
|
return 0, nil, fmt.Errorf("invalid IP address: %s", hostIP)
|
|
}
|
|
if isV6 {
|
|
sa := &unix.SockaddrInet6{Port: int(port)}
|
|
copy(sa.Addr[:], ip.To16())
|
|
return unix.AF_INET6, sa, nil
|
|
}
|
|
sa := &unix.SockaddrInet4{Port: int(port)}
|
|
copy(sa.Addr[:], ip.To4())
|
|
return unix.AF_INET, sa, nil
|
|
}
|
|
|
|
func setupSocketOpts(fd, domain int, hostIP string) error {
|
|
if domain == unix.AF_INET6 {
|
|
v6only := 1
|
|
if hostIP == "" {
|
|
v6only = 0
|
|
}
|
|
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, v6only); err != nil {
|
|
return fmt.Errorf("cannot set IPV6_V6ONLY: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func bindPortV4Fallback(protocol string, sockType int, port uint16) (*os.File, error) {
|
|
fd, err := unix.Socket(unix.AF_INET, sockType|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create socket for %s port %d: %w", protocol, port, err)
|
|
}
|
|
if err := unix.Bind(fd, &unix.SockaddrInet4{Port: int(port)}); err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("cannot bind %s port %s: %w", protocol, net.JoinHostPort("", strconv.FormatUint(uint64(port), 10)), err)
|
|
}
|
|
return os.NewFile(uintptr(fd), fmt.Sprintf("reservation-%s-%d", protocol, port)), nil
|
|
}
|
|
|
|
var (
|
|
regexPermissionDenied = regexp.Delayed("(?i).*permission denied.*|.*operation not permitted.*")
|
|
regexNotFound = regexp.Delayed("(?i).*executable file not found in.*|.*no such file or directory.*|.*open executable.*")
|
|
regexProcAttr = regexp.Delayed("`/proc/[a-z0-9-].+/attr.*`")
|
|
)
|
|
|
|
func getOCIRuntimeError(name, runtimeMsg string) error {
|
|
includeFullOutput := logrus.GetLevel() == logrus.DebugLevel
|
|
|
|
if match := regexPermissionDenied.FindString(runtimeMsg); match != "" {
|
|
errStr := match
|
|
if includeFullOutput {
|
|
errStr = runtimeMsg
|
|
}
|
|
return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimePermissionDenied)
|
|
}
|
|
if match := regexNotFound.FindString(runtimeMsg); match != "" {
|
|
errStr := match
|
|
if includeFullOutput {
|
|
errStr = runtimeMsg
|
|
}
|
|
return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimeNotFound)
|
|
}
|
|
if match := regexProcAttr.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)
|
|
}
|