Skip to content

Commit

Permalink
Merge pull request #23446 from Luap99/bind-ports
Browse files Browse the repository at this point in the history
libpod: bind ports before network setup
  • Loading branch information
openshift-merge-bot[bot] authored Jul 30, 2024
2 parents 0f093e5 + 77081df commit aa077cd
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 6 deletions.
4 changes: 4 additions & 0 deletions libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ type Container struct {
rootlessPortSyncR *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
// options when calling network setup/teardown. This should be used for
// container restore or network reload for example. Leave this nil if
Expand Down
9 changes: 9 additions & 0 deletions libpod/container_internal_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func (c *Container) prepare() error {
// Set up network namespace if not already set up
noNetNS := c.state.NetNS == ""
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
c.reservedPorts, createNetNSErr = c.bindPorts()
if createNetNSErr != nil {
return
}
ctrNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
if createNetNSErr != nil {
return
Expand Down Expand Up @@ -112,6 +116,11 @@ func (c *Container) prepare() error {
logrus.Errorf("Preparing container %s: %v", c.ID(), createErr)
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
}

Expand Down
10 changes: 10 additions & 0 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func (c *Container) prepare() error {
// Set up network namespace if not already set up
noNetNS := c.state.NetNS == ""
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
c.reservedPorts, createNetNSErr = c.bindPorts()
if createNetNSErr != nil {
return
}

netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
if createNetNSErr != nil {
return
Expand Down Expand Up @@ -148,6 +153,11 @@ func (c *Container) prepare() error {
}

if createErr != nil {
for _, f := range c.reservedPorts {
// make sure to close all ports again on errors
f.Close()
}
c.reservedPorts = nil
return createErr
}

Expand Down
11 changes: 11 additions & 0 deletions libpod/networking_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package libpod
import (
"errors"
"fmt"
"os"
"regexp"
"slices"
"sort"
Expand All @@ -21,6 +22,16 @@ import (
"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.
// 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.
Expand Down
11 changes: 8 additions & 3 deletions libpod/oci_conmon_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -1216,17 +1216,22 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
cmd.Env = append(cmd.Env, conmonEnv...)
cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe)

if r.reservePorts && !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
ports, err := bindPorts(ctr.convertPortMappings())
if ctr.config.PostConfigureNetNS {
// 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 {
return 0, err
}
filesToClose = append(filesToClose, ports...)

// 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
// process cannot use them.
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() {
Expand Down
7 changes: 6 additions & 1 deletion libpod/oci_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ func bindPorts(ports []types.PortMapping) ([]*os.File, error) {
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
// 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)
Expand Down
13 changes: 11 additions & 2 deletions test/system/500-networking.bats
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,25 @@ load helpers.network
$IMAGE /bin/busybox-extras httpd -f -p 80
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
run_podman exec -i myweb sh -c "cat > index2.txt" <<<"$random_2"
# ...verify its contents as seen from container.
run_podman exec -i myweb cat /var/www/index2.txt
is "$output" "$random_2" "exec cat index2.txt"

# 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"
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"

# Verify http contents: wget from a second container
Expand Down

0 comments on commit aa077cd

Please sign in to comment.