Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v5.2-rhel] Fix exposed ports #24397

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions libpod/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver
return nil, err
}
data.NetworkSettings = networkConfig
// Ports in NetworkSettings includes exposed ports for network modes that are not host,
// and not container.
if !(c.config.NetNsCtr != "" || c.NetworkMode() == "host") {
addInspectPortsExpose(c.config.ExposedPorts, data.NetworkSettings.Ports)
}

inspectConfig := c.generateInspectContainerConfig(ctrSpec)
data.Config = inspectConfig
Expand Down Expand Up @@ -438,6 +443,25 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp

ctrConfig.SdNotifyMode = c.config.SdNotifyMode
ctrConfig.SdNotifySocket = c.config.SdNotifySocket

// Exposed ports consists of all exposed ports and all port mappings for
// this container. It does *NOT* follow to another container if we share
// the network namespace.
exposedPorts := make(map[string]struct{})
for port, protocols := range c.config.ExposedPorts {
for _, proto := range protocols {
exposedPorts[fmt.Sprintf("%d/%s", port, proto)] = struct{}{}
}
}
for _, mapping := range c.config.PortMappings {
for i := uint16(0); i < mapping.Range; i++ {
exposedPorts[fmt.Sprintf("%d/%s", mapping.ContainerPort+i, mapping.Protocol)] = struct{}{}
}
}
if len(exposedPorts) > 0 {
ctrConfig.ExposedPorts = exposedPorts
}

return ctrConfig
}

Expand Down
8 changes: 8 additions & 0 deletions libpod/container_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package libpod

import (
"fmt"
"strings"

"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/pkg/shortnames"
Expand Down Expand Up @@ -175,6 +176,13 @@ func (c *Container) validate() error {
return fmt.Errorf("cannot set a startup healthcheck when there is no regular healthcheck: %w", define.ErrInvalidArg)
}

// Ensure all ports list a single protocol
for _, p := range c.config.PortMappings {
if strings.Contains(p.Protocol, ",") {
return fmt.Errorf("each port mapping must define a single protocol, got a comma-separated list for container port %d (protocols requested %q): %w", p.ContainerPort, p.Protocol, define.ErrInvalidArg)
}
}

return nil
}

Expand Down
2 changes: 2 additions & 0 deletions libpod/define/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ type InspectContainerConfig struct {
SdNotifyMode string `json:"sdNotifyMode,omitempty"`
// SdNotifySocket is the NOTIFY_SOCKET in use by/configured for the container.
SdNotifySocket string `json:"sdNotifySocket,omitempty"`
// ExposedPorts includes ports the container has exposed.
ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"`

// V4PodmanCompatMarshal indicates that the json marshaller should
// use the old v4 inspect format to keep API compatibility.
Expand Down
2 changes: 1 addition & 1 deletion libpod/networking_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
}

settings := new(define.InspectNetworkSettings)
settings.Ports = makeInspectPorts(c.config.PortMappings, c.config.ExposedPorts)
settings.Ports = makeInspectPortBindings(c.config.PortMappings)

networks, err := c.networks()
if err != nil {
Expand Down
17 changes: 15 additions & 2 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption {
// namespace with a minimal configuration.
// An optional array of port mappings can be provided.
// Conflicts with WithNetNSFrom().
func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks map[string]nettypes.PerNetworkOptions) CtrCreateOption {
func WithNetNS(portMappings []nettypes.PortMapping, postConfigureNetNS bool, netmode string, networks map[string]nettypes.PerNetworkOptions) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
Expand All @@ -1075,7 +1075,6 @@ func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]st
ctr.config.NetMode = namespaces.NetworkMode(netmode)
ctr.config.CreateNetNS = true
ctr.config.PortMappings = portMappings
ctr.config.ExposedPorts = exposedPorts

if !ctr.config.NetMode.IsBridge() && len(networks) > 0 {
return errors.New("cannot use networks when network mode is not bridge")
Expand All @@ -1086,6 +1085,20 @@ func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]st
}
}

// WithExposedPorts includes a set of ports that were exposed by the image in
// the container config, e.g. for display when the container is inspected.
func WithExposedPorts(exposedPorts map[uint16][]string) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}

ctr.config.ExposedPorts = exposedPorts

return nil
}
}

// WithNetworkOptions sets additional options for the networks.
func WithNetworkOptions(options map[string][]string) CtrCreateOption {
return func(ctr *Container) error {
Expand Down
15 changes: 7 additions & 8 deletions libpod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,8 @@ func writeHijackHeader(r *http.Request, conn io.Writer, tty bool) {
}
}

// Convert OCICNI port bindings into Inspect-formatted port bindings.
// Generate inspect-formatted port mappings from the format used in our config file
func makeInspectPortBindings(bindings []types.PortMapping) map[string][]define.InspectHostPort {
return makeInspectPorts(bindings, nil)
}

// Convert OCICNI port bindings into Inspect-formatted port bindings with exposed, but not bound ports set to nil.
func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort {
portBindings := make(map[string][]define.InspectHostPort)
for _, port := range bindings {
protocols := strings.Split(port.Protocol, ",")
Expand All @@ -249,7 +244,12 @@ func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string)
}
}
}
// add exposed ports without host port information to match docker

return portBindings
}

// Add exposed ports to inspect port bindings. These must be done on a per-container basis, not per-netns basis.
func addInspectPortsExpose(expose map[uint16][]string, portBindings map[string][]define.InspectHostPort) {
for port, protocols := range expose {
for _, protocol := range protocols {
key := fmt.Sprintf("%d/%s", port, protocol)
Expand All @@ -258,7 +258,6 @@ func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string)
}
}
}
return portBindings
}

// Write a given string to a new file at a given path.
Expand Down
23 changes: 10 additions & 13 deletions pkg/api/handlers/compat/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,19 +535,6 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
}
stopTimeout := int(l.StopTimeout())

exposedPorts := make(nat.PortSet)
for ep := range inspect.NetworkSettings.Ports {
port, proto, ok := strings.Cut(ep, "/")
if !ok {
return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep)
}
exposedPort, err := nat.NewPort(proto, port)
if err != nil {
return nil, err
}
exposedPorts[exposedPort] = struct{}{}
}

var healthcheck *container.HealthConfig
if inspect.Config.Healthcheck != nil {
healthcheck = &container.HealthConfig{
Expand All @@ -559,6 +546,16 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
}
}

// Apparently the compiler can't convert a map[string]struct{} into a nat.PortSet
// (Despite a nat.PortSet being that exact struct with some types added)
var exposedPorts nat.PortSet
if len(inspect.Config.ExposedPorts) > 0 {
exposedPorts = make(nat.PortSet)
for p := range inspect.Config.ExposedPorts {
exposedPorts[nat.Port(p)] = struct{}{}
}
}

config := container.Config{
Hostname: l.Hostname(),
Domainname: inspect.Config.DomainName,
Expand Down
26 changes: 10 additions & 16 deletions pkg/specgen/generate/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,13 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.

postConfigureNetNS := needPostConfigureNetNS(s)

// Network
portMappings, expose, err := createPortMappings(s, imageData)
if err != nil {
return nil, err
}
toReturn = append(toReturn, libpod.WithExposedPorts(expose))

switch s.NetNS.NSMode {
case specgen.FromPod:
if pod == nil || infraCtr == nil {
Expand All @@ -316,28 +323,15 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
}
case specgen.Slirp:
portMappings, expose, err := createPortMappings(s, imageData)
if err != nil {
return nil, err
}
val := "slirp4netns"
if s.NetNS.Value != "" {
val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
}
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
case specgen.Pasta:
portMappings, expose, err := createPortMappings(s, imageData)
if err != nil {
return nil, err
}
val := "pasta"
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
case specgen.Bridge, specgen.Private, specgen.Default:
portMappings, expose, err := createPortMappings(s, imageData)
if err != nil {
return nil, err
}

rtConfig, err := rt.GetConfigNoCopy()
if err != nil {
return nil, err
Expand All @@ -364,7 +358,7 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
s.Networks[rtConfig.Network.DefaultNetwork] = opts
delete(s.Networks, "default")
}
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.Networks))
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.Networks))
}

if s.UseImageHosts != nil && *s.UseImageHosts {
Expand Down
26 changes: 25 additions & 1 deletion test/e2e/container_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package integration

import (
"fmt"
"os"
"path/filepath"

Expand Down Expand Up @@ -35,7 +36,9 @@ var _ = Describe("Podman container inspect", func() {

Expect(data).To(HaveLen(1))
Expect(data[0].NetworkSettings.Ports).
To(Equal(map[string][]define.InspectHostPort{"8787/udp": nil}))
To(Equal(map[string][]define.InspectHostPort{"8787/udp": nil, "99/sctp": nil}))
Expect(data[0].Config.ExposedPorts).
To(Equal(map[string]struct{}{"8787/udp": {}, "99/sctp": {}}))

session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"})
session.WaitWithDefaultTimeout()
Expand All @@ -60,6 +63,27 @@ var _ = Describe("Podman container inspect", func() {
Expect(session.OutputToString()).To(Equal("80/tcp, 8989/tcp"))
})

It("podman inspect exposed ports includes published ports", func() {
c1 := "ctr1"
c1s := podmanTest.Podman([]string{"run", "-d", "--expose", "22/tcp", "-p", "8080:80/tcp", "--name", c1, ALPINE, "top"})
c1s.WaitWithDefaultTimeout()
Expect(c1s).Should(ExitCleanly())

c2 := "ctr2"
c2s := podmanTest.Podman([]string{"run", "-d", "--net", fmt.Sprintf("container:%s", c1), "--name", c2, ALPINE, "top"})
c2s.WaitWithDefaultTimeout()
Expect(c2s).Should(ExitCleanly())

data1 := podmanTest.InspectContainer(c1)
Expect(data1).To(HaveLen(1))
Expect(data1[0].Config.ExposedPorts).
To(Equal(map[string]struct{}{"22/tcp": {}, "80/tcp": {}}))

data2 := podmanTest.InspectContainer(c2)
Expect(data2).To(HaveLen(1))
Expect(data2[0].Config.ExposedPorts).To(BeNil())
})

It("podman inspect shows volumes-from with mount options", func() {
ctr1 := "volfctr"
ctr2 := "voltctr"
Expand Down
37 changes: 37 additions & 0 deletions test/e2e/run_networking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,43 @@ EXPOSE 2004-2005/tcp`, ALPINE)
Expect(inspectOut[0].HostConfig.PublishAllPorts).To(BeTrue())
})

It("podman run --net=host --expose includes ports in inspect output", func() {
containerName := "testctr"
session := podmanTest.Podman([]string{"run", "--net=host", "--name", containerName, "-d", "--expose", "8080/tcp", NGINX_IMAGE, "sleep", "+inf"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())

inspectOut := podmanTest.InspectContainer(containerName)
Expect(inspectOut).To(HaveLen(1))

// Ports is empty. ExposedPorts is not.
Expect(inspectOut[0].NetworkSettings.Ports).To(BeEmpty())

// 80 from the image, 8080 from the expose
Expect(inspectOut[0].Config.ExposedPorts).To(HaveLen(2))
Expect(inspectOut[0].Config.ExposedPorts).To(HaveKey("80/tcp"))
Expect(inspectOut[0].Config.ExposedPorts).To(HaveKey("8080/tcp"))
})

It("podman run --net=container --expose exposed port from own container", func() {
ctr1 := "test1"
session1 := podmanTest.Podman([]string{"run", "-d", "--name", ctr1, "--expose", "8080/tcp", ALPINE, "top"})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(ExitCleanly())

ctr2 := "test2"
session2 := podmanTest.Podman([]string{"run", "-d", "--name", ctr2, "--net", fmt.Sprintf("container:%s", ctr1), "--expose", "8090/tcp", ALPINE, "top"})
session2.WaitWithDefaultTimeout()
Expect(session2).Should(ExitCleanly())

inspectOut := podmanTest.InspectContainer(ctr2)
Expect(inspectOut).To(HaveLen(1))
// Ports will not be populated. ExposedPorts will be.
Expect(inspectOut[0].NetworkSettings.Ports).To(BeEmpty())
Expect(inspectOut[0].Config.ExposedPorts).To(HaveLen(1))
Expect(inspectOut[0].Config.ExposedPorts).To(HaveKey("8090/tcp"))
})

It("podman run -p 127.0.0.1::8980/udp", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1::8980/udp", "--name", name, ALPINE, "/bin/sh"})
Expand Down