Files
podman/libpod/oci_util.go
Paul Holzinger 77081df8cd libpod: bind ports before network setup
We bind ports to ensure there are no conflicts and we leak them into
conmon to keep them open. However we bound the ports after the network
was set up so it was possible for a second network setup to overwrite
the firewall configs of a previous container as it failed only later
when binding the port. As such we must ensure we bind before the network
is set up.

This is not so simple because we still have to take care of
PostConfigureNetNS bool in which case the network set up happens after
we launch conmon. Thus we end up with two different conditions.

Also it is possible that we "leak" the ports that are set on the
container until the garbage collector will close them. This is not
perfect but the alternative is adding special error handling on each
function exit after prepare until we start conmon which is a lot of work
to do correctly.

Fixes https://issues.redhat.com/browse/RHEL-50746

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2024-07-30 14:39:08 +02:00

176 lines
4.9 KiB
Go

//go:build !remote
package libpod
import (
"fmt"
"net"
"os"
"regexp"
"strings"
"time"
"github.com/containers/common/libnetwork/types"
"github.com/containers/podman/v5/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 {
// close all open ports in case of early error so we do not
// rely garbage collector to close them
for _, f := range files {
f.Close()
}
return nil, 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)
}