mirror of
https://github.com/containers/podman.git
synced 2025-08-06 11:32:07 +08:00
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>
This commit is contained in:
@ -118,6 +118,10 @@ type Container struct {
|
|||||||
rootlessPortSyncR *os.File
|
rootlessPortSyncR *os.File
|
||||||
rootlessPortSyncW *os.File
|
rootlessPortSyncW *os.File
|
||||||
|
|
||||||
|
// reservedPorts contains the fds for the bound ports when using the
|
||||||
|
// bridge network mode as root.
|
||||||
|
reservedPorts []*os.File
|
||||||
|
|
||||||
// perNetworkOpts should be set when you want to use special network
|
// perNetworkOpts should be set when you want to use special network
|
||||||
// options when calling network setup/teardown. This should be used for
|
// options when calling network setup/teardown. This should be used for
|
||||||
// container restore or network reload for example. Leave this nil if
|
// container restore or network reload for example. Leave this nil if
|
||||||
|
@ -50,6 +50,10 @@ func (c *Container) prepare() error {
|
|||||||
// Set up network namespace if not already set up
|
// Set up network namespace if not already set up
|
||||||
noNetNS := c.state.NetNS == ""
|
noNetNS := c.state.NetNS == ""
|
||||||
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
|
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
|
||||||
|
c.reservedPorts, createNetNSErr = c.bindPorts()
|
||||||
|
if createNetNSErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
ctrNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
ctrNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
||||||
if createNetNSErr != nil {
|
if createNetNSErr != nil {
|
||||||
return
|
return
|
||||||
@ -112,6 +116,11 @@ func (c *Container) prepare() error {
|
|||||||
logrus.Errorf("Preparing container %s: %v", c.ID(), createErr)
|
logrus.Errorf("Preparing container %s: %v", c.ID(), createErr)
|
||||||
createErr = fmt.Errorf("cleaning up container %s network after setup failure: %w", c.ID(), err)
|
createErr = fmt.Errorf("cleaning up container %s network after setup failure: %w", c.ID(), err)
|
||||||
}
|
}
|
||||||
|
for _, f := range c.reservedPorts {
|
||||||
|
// make sure to close all ports again on errors
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
c.reservedPorts = nil
|
||||||
return createErr
|
return createErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,11 @@ func (c *Container) prepare() error {
|
|||||||
// Set up network namespace if not already set up
|
// Set up network namespace if not already set up
|
||||||
noNetNS := c.state.NetNS == ""
|
noNetNS := c.state.NetNS == ""
|
||||||
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
|
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
|
||||||
|
c.reservedPorts, createNetNSErr = c.bindPorts()
|
||||||
|
if createNetNSErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
||||||
if createNetNSErr != nil {
|
if createNetNSErr != nil {
|
||||||
return
|
return
|
||||||
@ -148,6 +153,11 @@ func (c *Container) prepare() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createErr != nil {
|
if createErr != nil {
|
||||||
|
for _, f := range c.reservedPorts {
|
||||||
|
// make sure to close all ports again on errors
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
c.reservedPorts = nil
|
||||||
return createErr
|
return createErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ package libpod
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@ -21,6 +22,16 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// bindPorts ports to keep them open via conmon so no other process can use them and we can check if they are in use.
|
||||||
|
// Note in all cases it is important that we bind before setting up the network to avoid issues were we add firewall
|
||||||
|
// rules before we even "own" the port.
|
||||||
|
func (c *Container) bindPorts() ([]*os.File, error) {
|
||||||
|
if !c.runtime.config.Engine.EnablePortReservation || rootless.IsRootless() || !c.config.NetMode.IsBridge() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return bindPorts(c.convertPortMappings())
|
||||||
|
}
|
||||||
|
|
||||||
// convertPortMappings will remove the HostIP part from the ports when running inside podman machine.
|
// convertPortMappings will remove the HostIP part from the ports when running inside podman machine.
|
||||||
// This is needed because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
|
// This is needed because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
|
||||||
// For machine the HostIP must only be used by gvproxy and never in the VM.
|
// For machine the HostIP must only be used by gvproxy and never in the VM.
|
||||||
|
@ -1216,17 +1216,22 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
|
|||||||
cmd.Env = append(cmd.Env, conmonEnv...)
|
cmd.Env = append(cmd.Env, conmonEnv...)
|
||||||
cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe)
|
cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe)
|
||||||
|
|
||||||
if r.reservePorts && !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
|
if ctr.config.PostConfigureNetNS {
|
||||||
ports, err := bindPorts(ctr.convertPortMappings())
|
// netns was not setup yet but we have to bind ports now so we can leak the fd to conmon
|
||||||
|
ports, err := ctr.bindPorts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
filesToClose = append(filesToClose, ports...)
|
filesToClose = append(filesToClose, ports...)
|
||||||
|
|
||||||
// Leak the port we bound in the conmon process. These fd's won't be used
|
// Leak the port we bound in the conmon process. These fd's won't be used
|
||||||
// by the container and conmon will keep the ports busy so that another
|
// by the container and conmon will keep the ports busy so that another
|
||||||
// process cannot use them.
|
// process cannot use them.
|
||||||
cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
|
||||||
|
} else {
|
||||||
|
// ports were bound in ctr.prepare() as we must do it before the netns setup
|
||||||
|
filesToClose = append(filesToClose, ctr.reservedPorts...)
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.reservedPorts...)
|
||||||
|
ctr.reservedPorts = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctr.config.NetMode.IsSlirp4netns() || rootless.IsRootless() {
|
if ctr.config.NetMode.IsSlirp4netns() || rootless.IsRootless() {
|
||||||
|
@ -46,7 +46,12 @@ func bindPorts(ports []types.PortMapping) ([]*os.File, error) {
|
|||||||
for i := uint16(0); i < port.Range; i++ {
|
for i := uint16(0); i < port.Range; i++ {
|
||||||
f, err := bindPort(protocol, port.HostIP, port.HostPort+i, isV6, &sctpWarning)
|
f, err := bindPort(protocol, port.HostIP, port.HostPort+i, isV6, &sctpWarning)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return files, err
|
// 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 {
|
if f != nil {
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
|
@ -54,6 +54,15 @@ load helpers.network
|
|||||||
$IMAGE /bin/busybox-extras httpd -f -p 80
|
$IMAGE /bin/busybox-extras httpd -f -p 80
|
||||||
cid=$output
|
cid=$output
|
||||||
|
|
||||||
|
# Try to bind the same port again, this must fail.
|
||||||
|
# regression test for https://issues.redhat.com/browse/RHEL-50746
|
||||||
|
# which caused this command to overwrite the firewall rules as root
|
||||||
|
# causing the curl commands below to fail
|
||||||
|
run_podman 126 run --rm -p "$HOST_PORT:80" $IMAGE true
|
||||||
|
# Note error messages differ between root/rootless, so only check port
|
||||||
|
# and the part of the error text that is common.
|
||||||
|
assert "$output" =~ "$HOST_PORT.*ddress already in use" "port in use"
|
||||||
|
|
||||||
# In that container, create a second file, using exec and redirection
|
# In that container, create a second file, using exec and redirection
|
||||||
run_podman exec -i myweb sh -c "cat > index2.txt" <<<"$random_2"
|
run_podman exec -i myweb sh -c "cat > index2.txt" <<<"$random_2"
|
||||||
# ...verify its contents as seen from container.
|
# ...verify its contents as seen from container.
|
||||||
@ -61,9 +70,9 @@ load helpers.network
|
|||||||
is "$output" "$random_2" "exec cat index2.txt"
|
is "$output" "$random_2" "exec cat index2.txt"
|
||||||
|
|
||||||
# Verify http contents: curl from localhost
|
# Verify http contents: curl from localhost
|
||||||
run curl -s -S $SERVER/index.txt
|
run curl --max-time 3 -s -S $SERVER/index.txt
|
||||||
is "$output" "$random_1" "curl 127.0.0.1:/index.txt"
|
is "$output" "$random_1" "curl 127.0.0.1:/index.txt"
|
||||||
run curl -s -S $SERVER/index2.txt
|
run curl --max-time 3 -s -S $SERVER/index2.txt
|
||||||
is "$output" "$random_2" "curl 127.0.0.1:/index2.txt"
|
is "$output" "$random_2" "curl 127.0.0.1:/index2.txt"
|
||||||
|
|
||||||
# Verify http contents: wget from a second container
|
# Verify http contents: wget from a second container
|
||||||
|
Reference in New Issue
Block a user