mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00

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>
176 lines
4.9 KiB
Go
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)
|
|
}
|