summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/boltdb_state_internal.go9
-rw-r--r--libpod/common_test.go4
-rw-r--r--libpod/container.go2
-rw-r--r--libpod/container_config.go12
-rw-r--r--libpod/container_internal.go13
-rw-r--r--libpod/kube.go51
-rw-r--r--libpod/network/types/network.go1
-rw-r--r--libpod/networking_linux.go330
-rw-r--r--libpod/networking_linux_test.go323
-rw-r--r--libpod/networking_slirp4netns.go4
-rw-r--r--libpod/oci_util.go169
-rw-r--r--libpod/options.go17
-rw-r--r--libpod/state_test.go40
-rw-r--r--libpod/util.go22
14 files changed, 729 insertions, 268 deletions
diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go
index 3e3c17a9e..e71d82736 100644
--- a/libpod/boltdb_state_internal.go
+++ b/libpod/boltdb_state_internal.go
@@ -384,6 +384,15 @@ func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig,
return errors.Wrapf(err, "error unmarshalling container %s config", string(id))
}
+ // convert ports to the new format if needed
+ if len(config.ContainerNetworkConfig.OldPortMappings) > 0 && len(config.ContainerNetworkConfig.PortMappings) == 0 {
+ config.ContainerNetworkConfig.PortMappings = ocicniPortsToNetTypesPorts(config.ContainerNetworkConfig.OldPortMappings)
+ // keep the OldPortMappings in case an user has to downgrade podman
+
+ // indicate the the config was modified and should be written back to the db when possible
+ config.rewrite = true
+ }
+
return nil
}
diff --git a/libpod/common_test.go b/libpod/common_test.go
index 4662a33bd..67e29c265 100644
--- a/libpod/common_test.go
+++ b/libpod/common_test.go
@@ -41,18 +41,20 @@ func getTestContainer(id, name string, manager lock.Manager) (*Container, error)
ContainerNetworkConfig: ContainerNetworkConfig{
DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")},
DNSSearch: []string{"example.com", "example.example.com"},
- PortMappings: []types.OCICNIPortMapping{
+ PortMappings: []types.PortMapping{
{
HostPort: 80,
ContainerPort: 90,
Protocol: "tcp",
HostIP: "192.168.3.3",
+ Range: 1,
},
{
HostPort: 100,
ContainerPort: 110,
Protocol: "udp",
HostIP: "192.168.4.4",
+ Range: 1,
},
},
},
diff --git a/libpod/container.go b/libpod/container.go
index 4d15c04c5..86989a02f 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -465,7 +465,7 @@ func (c *Container) NewNetNS() bool {
// PortMappings returns the ports that will be mapped into a container if
// a new network namespace is created
// If NewNetNS() is false, this value is unused
-func (c *Container) PortMappings() ([]types.OCICNIPortMapping, error) {
+func (c *Container) PortMappings() ([]types.PortMapping, error) {
// First check if the container belongs to a network namespace (like a pod)
if len(c.config.NetNsCtr) > 0 {
netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr)
diff --git a/libpod/container_config.go b/libpod/container_config.go
index 8f803d744..412be835f 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -78,6 +78,11 @@ type ContainerConfig struct {
// These containers must be started before this container is started.
Dependencies []string
+ // rewrite is an internal bool to indicate that the config was modified after
+ // a read from the db, e.g. to migrate config fields after an upgrade.
+ // This field should never be written to the db, the json tag ensures this.
+ rewrite bool `json:"-"`
+
// embedded sub-configs
ContainerRootFSConfig
ContainerSecurityConfig
@@ -232,7 +237,12 @@ type ContainerNetworkConfig struct {
// PortMappings are the ports forwarded to the container's network
// namespace
// These are not used unless CreateNetNS is true
- PortMappings []types.OCICNIPortMapping `json:"portMappings,omitempty"`
+ PortMappings []types.PortMapping `json:"newPortMappings,omitempty"`
+ // OldPortMappings are the ports forwarded to the container's network
+ // namespace. As of podman 4.0 this field is deprecated, use PortMappings
+ // instead. The db will convert the old ports to the new structure for you.
+ // These are not used unless CreateNetNS is true
+ OldPortMappings []types.OCICNIPortMapping `json:"portMappings,omitempty"`
// ExposedPorts are the ports which are exposed but not forwarded
// into the container.
// The map key is the port and the string slice contains the protocols,
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index b9805faa3..19b48e14b 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -667,6 +667,19 @@ func (c *Container) refresh() error {
c.state.NetworkStatus = nil
c.state.NetworkStatusOld = nil
+ // Rewrite the config if necessary.
+ // Podman 4.0 uses a new port format in the config.
+ // getContainerConfigFromDB() already converted the old ports to the new one
+ // but it did not write the config to the db back for performance reasons.
+ // If a rewrite must happen the config.rewrite field is set to true.
+ if c.config.rewrite {
+ // SafeRewriteContainerConfig must be used with care. Make sure to not change config fields by accident.
+ if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil {
+ return errors.Wrapf(err, "failed to rewrite the config for container %s", c.config.ID)
+ }
+ c.config.rewrite = false
+ }
+
if err := c.save(); err != nil {
return errors.Wrapf(err, "error refreshing state for container %s", c.ID())
}
diff --git a/libpod/kube.go b/libpod/kube.go
index ad5acea78..e0ed911af 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -75,7 +75,7 @@ func (p *Pod) GenerateForKube(ctx context.Context) (*v1.Pod, []v1.ServicePort, e
Hostnames: []string{hostSli[0]},
})
}
- ports, err = ocicniPortMappingToContainerPort(infraContainer.config.PortMappings)
+ ports, err = portMappingToContainerPort(infraContainer.config.PortMappings)
if err != nil {
return nil, servicePorts, err
}
@@ -452,7 +452,7 @@ func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []
if err != nil {
return kubeContainer, kubeVolumes, nil, annotations, err
}
- ports, err := ocicniPortMappingToContainerPort(portmappings)
+ ports, err := portMappingToContainerPort(portmappings)
if err != nil {
return kubeContainer, kubeVolumes, nil, annotations, err
}
@@ -588,29 +588,36 @@ func containerToV1Container(ctx context.Context, c *Container) (v1.Container, []
return kubeContainer, kubeVolumes, &dns, annotations, nil
}
-// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts
+// portMappingToContainerPort takes an portmapping and converts
// it to a v1.ContainerPort format for kube output
-func ocicniPortMappingToContainerPort(portMappings []types.OCICNIPortMapping) ([]v1.ContainerPort, error) {
+func portMappingToContainerPort(portMappings []types.PortMapping) ([]v1.ContainerPort, error) {
containerPorts := make([]v1.ContainerPort, 0, len(portMappings))
for _, p := range portMappings {
- var protocol v1.Protocol
- switch strings.ToUpper(p.Protocol) {
- case "TCP":
- // do nothing as it is the default protocol in k8s, there is no need to explicitly
- // add it to the generated yaml
- case "UDP":
- protocol = v1.ProtocolUDP
- default:
- return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol)
- }
- cp := v1.ContainerPort{
- // Name will not be supported
- HostPort: p.HostPort,
- HostIP: p.HostIP,
- ContainerPort: p.ContainerPort,
- Protocol: protocol,
- }
- containerPorts = append(containerPorts, cp)
+ protocols := strings.Split(p.Protocol, ",")
+ for _, proto := range protocols {
+ var protocol v1.Protocol
+ switch strings.ToUpper(proto) {
+ case "TCP":
+ // do nothing as it is the default protocol in k8s, there is no need to explicitly
+ // add it to the generated yaml
+ case "UDP":
+ protocol = v1.ProtocolUDP
+ case "SCTP":
+ protocol = v1.ProtocolSCTP
+ default:
+ return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol)
+ }
+ for i := uint16(0); i < p.Range; i++ {
+ cp := v1.ContainerPort{
+ // Name will not be supported
+ HostPort: int32(p.HostPort + i),
+ HostIP: p.HostIP,
+ ContainerPort: int32(p.ContainerPort + i),
+ Protocol: protocol,
+ }
+ containerPorts = append(containerPorts, cp)
+ }
+ }
}
return containerPorts, nil
}
diff --git a/libpod/network/types/network.go b/libpod/network/types/network.go
index 5cf523e20..ba5e018fd 100644
--- a/libpod/network/types/network.go
+++ b/libpod/network/types/network.go
@@ -99,6 +99,7 @@ func (n *IPNet) UnmarshalText(text []byte) error {
// that it adds the json marshal/unmarshal methods.
// This allows us to read the mac from a json string
// and a byte array.
+// swagger:model MacAddress
type HardwareAddr net.HardwareAddr
func (h *HardwareAddr) String() string {
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index e792a410c..ef261a438 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -11,6 +11,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
+ "sort"
"strconv"
"strings"
"syscall"
@@ -41,8 +42,11 @@ const (
// default slirp4ns subnet
defaultSlirp4netnsSubnet = "10.0.2.0/24"
- // rootlessCNINSName is the file name for the rootless network namespace bind mount
- rootlessCNINSName = "rootless-cni-ns"
+ // rootlessNetNsName is the file name for the rootless network namespace bind mount
+ rootlessNetNsName = "rootless-netns"
+
+ // rootlessNetNsSilrp4netnsPidFile is the name of the rootless netns slirp4netns pid file
+ rootlessNetNsSilrp4netnsPidFile = "rootless-netns-slirp4netns.pid"
// persistentCNIDir is the directory where the CNI files are stored
persistentCNIDir = "/var/lib/cni"
@@ -88,10 +92,7 @@ func (c *Container) getNetworkOptions() (types.NetworkOptions, error) {
ContainerID: c.config.ID,
ContainerName: getCNIPodName(c),
}
- // TODO remove ocicni PortMappings from container config and store as types PortMappings
- if len(c.config.PortMappings) > 0 {
- opts.PortMappings = ocicniPortsToNetTypesPorts(c.config.PortMappings)
- }
+ opts.PortMappings = c.config.PortMappings
networks, _, err := c.networks()
if err != nil {
return opts, err
@@ -136,21 +137,21 @@ func (c *Container) getNetworkOptions() (types.NetworkOptions, error) {
return opts, nil
}
-type RootlessCNI struct {
+type RootlessNetNS struct {
ns ns.NetNS
dir string
Lock lockfile.Locker
}
-// getPath will join the given path to the rootless cni dir
-func (r *RootlessCNI) getPath(path string) string {
+// getPath will join the given path to the rootless netns dir
+func (r *RootlessNetNS) getPath(path string) string {
return filepath.Join(r.dir, path)
}
-// Do - run the given function in the rootless cni ns.
+// Do - run the given function in the rootless netns.
// It does not lock the rootlessCNI lock, the caller
// should only lock when needed, e.g. for cni operations.
-func (r *RootlessCNI) Do(toRun func() error) error {
+func (r *RootlessNetNS) Do(toRun func() error) error {
err := r.ns.Do(func(_ ns.NetNS) error {
// Before we can run the given function,
// we have to setup all mounts correctly.
@@ -161,11 +162,11 @@ func (r *RootlessCNI) Do(toRun func() error) error {
// Because the plugins also need access to XDG_RUNTIME_DIR/netns some special setup is needed.
// The following bind mounts are needed
- // 1. XDG_RUNTIME_DIR/netns -> XDG_RUNTIME_DIR/rootless-cni/XDG_RUNTIME_DIR/netns
- // 2. /run/systemd -> XDG_RUNTIME_DIR/rootless-cni/run/systemd (only if it exists)
- // 3. XDG_RUNTIME_DIR/rootless-cni/resolv.conf -> /etc/resolv.conf or XDG_RUNTIME_DIR/rootless-cni/run/symlink/target
- // 4. XDG_RUNTIME_DIR/rootless-cni/var/lib/cni -> /var/lib/cni (if /var/lib/cni does not exists use the parent dir)
- // 5. XDG_RUNTIME_DIR/rootless-cni/run -> /run
+ // 1. XDG_RUNTIME_DIR -> XDG_RUNTIME_DIR/rootless-netns/XDG_RUNTIME_DIR
+ // 2. /run/systemd -> XDG_RUNTIME_DIR/rootless-netns/run/systemd (only if it exists)
+ // 3. XDG_RUNTIME_DIR/rootless-netns/resolv.conf -> /etc/resolv.conf or XDG_RUNTIME_DIR/rootless-netns/run/symlink/target
+ // 4. XDG_RUNTIME_DIR/rootless-netns/var/lib/cni -> /var/lib/cni (if /var/lib/cni does not exists use the parent dir)
+ // 5. XDG_RUNTIME_DIR/rootless-netns/run -> /run
// Create a new mount namespace,
// this must happen inside the netns thread.
@@ -174,16 +175,16 @@ func (r *RootlessCNI) Do(toRun func() error) error {
return errors.Wrapf(err, "cannot create a new mount namespace")
}
- netNsDir, err := netns.GetNSRunDir()
+ xdgRuntimeDir, err := util.GetRuntimeDir()
if err != nil {
- return errors.Wrap(err, "could not get network namespace directory")
+ return errors.Wrap(err, "could not get runtime directory")
}
- newNetNsDir := r.getPath(netNsDir)
+ newXDGRuntimeDir := r.getPath(xdgRuntimeDir)
// 1. Mount the netns into the new run to keep them accessible.
// Otherwise cni setup will fail because it cannot access the netns files.
- err = unix.Mount(netNsDir, newNetNsDir, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "")
+ err = unix.Mount(xdgRuntimeDir, newXDGRuntimeDir, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "")
if err != nil {
- return errors.Wrap(err, "failed to mount netns directory for rootless cni")
+ return errors.Wrap(err, "failed to mount runtime directory for rootless netns")
}
// 2. Also keep /run/systemd if it exists.
@@ -194,7 +195,7 @@ func (r *RootlessCNI) Do(toRun func() error) error {
newRunSystemd := r.getPath(runSystemd)
err = unix.Mount(runSystemd, newRunSystemd, "none", unix.MS_BIND|unix.MS_REC, "")
if err != nil {
- return errors.Wrap(err, "failed to mount /run/systemd directory for rootless cni")
+ return errors.Wrap(err, "failed to mount /run/systemd directory for rootless netns")
}
}
@@ -242,25 +243,25 @@ func (r *RootlessCNI) Do(toRun func() error) error {
rsr := r.getPath("/run/systemd/resolve")
err = unix.Mount("", rsr, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, "")
if err != nil {
- return errors.Wrapf(err, "failed to mount tmpfs on %q for rootless cni", rsr)
+ return errors.Wrapf(err, "failed to mount tmpfs on %q for rootless netns", rsr)
}
}
if strings.HasPrefix(resolvePath, "/run/") {
resolvePath = r.getPath(resolvePath)
err = os.MkdirAll(filepath.Dir(resolvePath), 0700)
if err != nil {
- return errors.Wrap(err, "failed to create rootless-cni resolv.conf directory")
+ return errors.Wrap(err, "failed to create rootless-netns resolv.conf directory")
}
// we want to bind mount on this file so we have to create the file first
_, err = os.OpenFile(resolvePath, os.O_CREATE|os.O_RDONLY, 0700)
if err != nil {
- return errors.Wrap(err, "failed to create rootless-cni resolv.conf file")
+ return errors.Wrap(err, "failed to create rootless-netns resolv.conf file")
}
}
// mount resolv.conf to make use of the host dns
err = unix.Mount(r.getPath("resolv.conf"), resolvePath, "none", unix.MS_BIND, "")
if err != nil {
- return errors.Wrap(err, "failed to mount resolv.conf for rootless cni")
+ return errors.Wrap(err, "failed to mount resolv.conf for rootless netns")
}
// 4. CNI plugins need access to /var/lib/cni and /run
@@ -285,14 +286,14 @@ func (r *RootlessCNI) Do(toRun func() error) error {
// make sure to mount var first
err = unix.Mount(varDir, varTarget, "none", unix.MS_BIND, "")
if err != nil {
- return errors.Wrapf(err, "failed to mount %s for rootless cni", varTarget)
+ return errors.Wrapf(err, "failed to mount %s for rootless netns", varTarget)
}
// 5. Mount the new prepared run dir to /run, it has to be recursive to keep the other bind mounts.
runDir := r.getPath("run")
err = unix.Mount(runDir, "/run", "none", unix.MS_BIND|unix.MS_REC, "")
if err != nil {
- return errors.Wrap(err, "failed to mount /run for rootless cni")
+ return errors.Wrap(err, "failed to mount /run for rootless netns")
}
// run the given function in the correct namespace
@@ -302,10 +303,11 @@ func (r *RootlessCNI) Do(toRun func() error) error {
return err
}
-// Cleanup the rootless cni namespace if needed.
+// Cleanup the rootless network namespace if needed.
// It checks if we have running containers with the bridge network mode.
-// Cleanup() will try to lock RootlessCNI, therefore you have to call it with an unlocked
-func (r *RootlessCNI) Cleanup(runtime *Runtime) error {
+// Cleanup() will try to lock RootlessNetNS, therefore you have to call
+// it with an unlocked lock.
+func (r *RootlessNetNS) Cleanup(runtime *Runtime) error {
_, err := os.Stat(r.dir)
if os.IsNotExist(err) {
// the directory does not exists no need for cleanup
@@ -314,8 +316,25 @@ func (r *RootlessCNI) Cleanup(runtime *Runtime) error {
r.Lock.Lock()
defer r.Lock.Unlock()
running := func(c *Container) bool {
+ // no bridge => no need to check
+ if !c.config.NetMode.IsBridge() {
+ return false
+ }
+
// we cannot use c.state() because it will try to lock the container
- // using c.state.State directly should be good enough for this use case
+ // locking is a problem because cleanup is called after net teardown
+ // at this stage the container is already locked.
+ // also do not try to lock only containers which are not currently in net
+ // teardown because this will result in an ABBA deadlock between the rootless
+ // cni lock and the container lock
+ // because we need to get the state we have to sync otherwise this will not
+ // work because the state is empty by default
+ // I do not like this but I do not see a better way at moment
+ err := c.syncContainer()
+ if err != nil {
+ return false
+ }
+
state := c.state.State
return state == define.ContainerStateRunning
}
@@ -323,101 +342,89 @@ func (r *RootlessCNI) Cleanup(runtime *Runtime) error {
if err != nil {
return err
}
- cleanup := true
- for _, ctr := range ctrs {
- if ctr.config.NetMode.IsBridge() {
- cleanup = false
- }
+ // no cleanup if we found containers
+ if len(ctrs) > 0 {
+ return nil
}
- if cleanup {
- // make sure the the cni results (cache) dir is empty
- // libpod instances with another root dir are not covered by the check above
- // this allows several libpod instances to use the same rootless cni ns
- contents, err := ioutil.ReadDir(r.getPath("var/lib/cni/results"))
- if (err == nil && len(contents) == 0) || os.IsNotExist(err) {
- logrus.Debug("Cleaning up rootless cni namespace")
- err = netns.UnmountNS(r.ns)
- if err != nil {
- return err
- }
- // make the following errors not fatal
- err = r.ns.Close()
- if err != nil {
- logrus.Error(err)
- }
- b, err := ioutil.ReadFile(r.getPath("rootless-cni-slirp4netns.pid"))
- if err == nil {
- var i int
- i, err = strconv.Atoi(string(b))
- if err == nil {
- // kill the slirp process so we do not leak it
- err = syscall.Kill(i, syscall.SIGTERM)
- }
- }
- if err != nil {
- logrus.Errorf("Failed to kill slirp4netns process: %s", err)
- }
- err = os.RemoveAll(r.dir)
- if err != nil {
- logrus.Error(err)
- }
- } else if err != nil && !os.IsNotExist(err) {
- logrus.Errorf("Could not read rootless cni directory, skipping cleanup: %s", err)
+ logrus.Debug("Cleaning up rootless network namespace")
+ err = netns.UnmountNS(r.ns)
+ if err != nil {
+ return err
+ }
+ // make the following errors not fatal
+ err = r.ns.Close()
+ if err != nil {
+ logrus.Error(err)
+ }
+ b, err := ioutil.ReadFile(r.getPath(rootlessNetNsSilrp4netnsPidFile))
+ if err == nil {
+ var i int
+ i, err = strconv.Atoi(string(b))
+ if err == nil {
+ // kill the slirp process so we do not leak it
+ err = syscall.Kill(i, syscall.SIGTERM)
}
}
+ if err != nil {
+ logrus.Errorf("Failed to kill slirp4netns process: %s", err)
+ }
+ err = os.RemoveAll(r.dir)
+ if err != nil {
+ logrus.Error(err)
+ }
return nil
}
-// GetRootlessCNINetNs returns the rootless cni object. If create is set to true
-// the rootless cni namespace will be created if it does not exists already.
+// GetRootlessNetNs returns the rootless netns object. If create is set to true
+// the rootless network namespace will be created if it does not exists already.
// If called as root it returns always nil.
// On success the returned RootlessCNI lock is locked and must be unlocked by the caller.
-func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
+func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) {
if !rootless.IsRootless() {
return nil, nil
}
- var rootlessCNINS *RootlessCNI
+ var rootlessNetNS *RootlessNetNS
runDir, err := util.GetRuntimeDir()
if err != nil {
return nil, err
}
- lfile := filepath.Join(runDir, "rootless-cni.lock")
+ lfile := filepath.Join(runDir, "rootless-netns.lock")
lock, err := lockfile.GetLockfile(lfile)
if err != nil {
- return nil, errors.Wrap(err, "failed to get rootless-cni lockfile")
+ return nil, errors.Wrap(err, "failed to get rootless-netns lockfile")
}
lock.Lock()
defer func() {
- // In case of an error (early exit) rootlessCNINS will be nil.
+ // In case of an error (early exit) rootlessNetNS will be nil.
// Make sure to unlock otherwise we could deadlock.
- if rootlessCNINS == nil {
+ if rootlessNetNS == nil {
lock.Unlock()
}
}()
- cniDir := filepath.Join(runDir, "rootless-cni")
- err = os.MkdirAll(cniDir, 0700)
+ rootlessNetNsDir := filepath.Join(runDir, rootlessNetNsName)
+ err = os.MkdirAll(rootlessNetNsDir, 0700)
if err != nil {
- return nil, errors.Wrap(err, "could not create rootless-cni directory")
+ return nil, errors.Wrap(err, "could not create rootless-netns directory")
}
nsDir, err := netns.GetNSRunDir()
if err != nil {
return nil, err
}
- path := filepath.Join(nsDir, rootlessCNINSName)
+ path := filepath.Join(nsDir, rootlessNetNsName)
ns, err := ns.GetNS(path)
if err != nil {
if !new {
// return a error if we could not get the namespace and should no create one
- return nil, errors.Wrap(err, "error getting rootless cni network namespace")
+ return nil, errors.Wrap(err, "error getting rootless network namespace")
}
// create a new namespace
- logrus.Debug("creating rootless cni network namespace")
- ns, err = netns.NewNSWithName(rootlessCNINSName)
+ logrus.Debug("creating rootless network namespace")
+ ns, err = netns.NewNSWithName(rootlessNetNsName)
if err != nil {
- return nil, errors.Wrap(err, "error creating rootless cni network namespace")
+ return nil, errors.Wrap(err, "error creating rootless network namespace")
}
// setup slirp4netns here
path := r.config.Engine.NetworkCmdPath
@@ -467,7 +474,7 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
// Leak one end of the pipe in slirp4netns
cmd.ExtraFiles = append(cmd.ExtraFiles, syncW)
- logPath := filepath.Join(r.config.Engine.TmpDir, "slirp4netns-rootless-cni.log")
+ logPath := filepath.Join(r.config.Engine.TmpDir, "slirp4netns-rootless-netns.log")
logFile, err := os.Create(logPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to open slirp4netns log file %s", logPath)
@@ -486,9 +493,9 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
// create pid file for the slirp4netns process
// this is need to kill the process in the cleanup
pid := strconv.Itoa(cmd.Process.Pid)
- err = ioutil.WriteFile(filepath.Join(cniDir, "rootless-cni-slirp4netns.pid"), []byte(pid), 0700)
+ err = ioutil.WriteFile(filepath.Join(rootlessNetNsDir, rootlessNetNsSilrp4netnsPidFile), []byte(pid), 0700)
if err != nil {
- errors.Wrap(err, "unable to write rootless-cni slirp4netns pid file")
+ errors.Wrap(err, "unable to write rootless-netns slirp4netns pid file")
}
defer func() {
@@ -529,43 +536,43 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
dnsOptions := resolvconf.GetOptions(conf.Content)
nameServers := resolvconf.GetNameservers(conf.Content)
- _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), append([]string{resolveIP.String()}, nameServers...), searchDomains, dnsOptions)
+ _, err = resolvconf.Build(filepath.Join(rootlessNetNsDir, "resolv.conf"), append([]string{resolveIP.String()}, nameServers...), searchDomains, dnsOptions)
if err != nil {
- return nil, errors.Wrap(err, "failed to create rootless cni resolv.conf")
+ return nil, errors.Wrap(err, "failed to create rootless netns resolv.conf")
}
// create cni directories to store files
// they will be bind mounted to the correct location in a extra mount ns
- err = os.MkdirAll(filepath.Join(cniDir, strings.TrimPrefix(persistentCNIDir, "/")), 0700)
+ err = os.MkdirAll(filepath.Join(rootlessNetNsDir, persistentCNIDir), 0700)
if err != nil {
- return nil, errors.Wrap(err, "could not create rootless-cni var directory")
+ return nil, errors.Wrap(err, "could not create rootless-netns var directory")
}
- runDir := filepath.Join(cniDir, "run")
+ runDir := filepath.Join(rootlessNetNsDir, "run")
err = os.MkdirAll(runDir, 0700)
if err != nil {
- return nil, errors.Wrap(err, "could not create rootless-cni run directory")
+ return nil, errors.Wrap(err, "could not create rootless-netns run directory")
}
// relabel the new run directory to the iptables /run label
// this is important, otherwise the iptables command will fail
err = label.Relabel(runDir, "system_u:object_r:iptables_var_run_t:s0", false)
if err != nil {
- return nil, errors.Wrap(err, "could not create relabel rootless-cni run directory")
+ return nil, errors.Wrap(err, "could not create relabel rootless-netns run directory")
}
// create systemd run directory
err = os.MkdirAll(filepath.Join(runDir, "systemd"), 0700)
if err != nil {
- return nil, errors.Wrap(err, "could not create rootless-cni systemd directory")
+ return nil, errors.Wrap(err, "could not create rootless-netns systemd directory")
}
// create the directory for the netns files at the same location
- // relative to the rootless-cni location
- err = os.MkdirAll(filepath.Join(cniDir, nsDir), 0700)
+ // relative to the rootless-netns location
+ err = os.MkdirAll(filepath.Join(rootlessNetNsDir, nsDir), 0700)
if err != nil {
- return nil, errors.Wrap(err, "could not create rootless-cni netns directory")
+ return nil, errors.Wrap(err, "could not create rootless-netns netns directory")
}
}
- // The CNI plugins need access to iptables in $PATH. As it turns out debian doesn't put
- // /usr/sbin in $PATH for rootless users. This will break rootless cni completely.
+ // The CNI plugins and netavark need access to iptables in $PATH. As it turns out debian doesn't put
+ // /usr/sbin in $PATH for rootless users. This will break rootless networking completely.
// We might break existing users and we cannot expect everyone to change their $PATH so
// lets add /usr/sbin to $PATH ourselves.
path = os.Getenv("PATH")
@@ -574,14 +581,14 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
os.Setenv("PATH", path)
}
- // Important set rootlessCNINS as last step.
+ // Important set rootlessNetNS as last step.
// Do not return any errors after this.
- rootlessCNINS = &RootlessCNI{
+ rootlessNetNS = &RootlessNetNS{
ns: ns,
- dir: cniDir,
+ dir: rootlessNetNsDir,
Lock: lock,
}
- return rootlessCNINS, nil
+ return rootlessNetNS, nil
}
// setPrimaryMachineIP is used for podman-machine and it sets
@@ -603,14 +610,14 @@ func setPrimaryMachineIP() error {
}
// setUpNetwork will set up the the networks, on error it will also tear down the cni
-// networks. If rootless it will join/create the rootless cni namespace.
+// networks. If rootless it will join/create the rootless network namespace.
func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) {
if r.config.MachineEnabled() {
if err := setPrimaryMachineIP(); err != nil {
return nil, err
}
}
- rootlessCNINS, err := r.GetRootlessCNINetNs(true)
+ rootlessNetNS, err := r.GetRootlessNetNs(true)
if err != nil {
return nil, err
}
@@ -619,11 +626,11 @@ func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string
results, err = r.network.Setup(ns, types.SetupOptions{NetworkOptions: opts})
return err
}
- // rootlessCNINS is nil if we are root
- if rootlessCNINS != nil {
- // execute the cni setup in the rootless net ns
- err = rootlessCNINS.Do(setUpPod)
- rootlessCNINS.Lock.Unlock()
+ // rootlessNetNS is nil if we are root
+ if rootlessNetNS != nil {
+ // execute the setup in the rootless net ns
+ err = rootlessNetNS.Do(setUpPod)
+ rootlessNetNS.Lock.Unlock()
} else {
err = setUpPod()
}
@@ -697,10 +704,10 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
return err
}
if len(networks) > 0 && len(ctr.config.PortMappings) > 0 {
- // set up port forwarder for CNI-in-slirp4netns
+ // set up port forwarder for rootless netns
netnsPath := ctr.state.NetNS.Path()
// TODO: support slirp4netns port forwarder as well
- // make sure to fix this container.handleRestartPolicy() as well
+ // make sure to fix this in container.handleRestartPolicy() as well
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
}
return nil
@@ -719,7 +726,7 @@ func (r *Runtime) setupNetNS(ctr *Container) error {
if err != nil {
return err
}
- nsPath = filepath.Join(nsPath, fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]))
+ nsPath = filepath.Join(nsPath, fmt.Sprintf("netns-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]))
if err := os.MkdirAll(filepath.Dir(nsPath), 0711); err != nil {
return err
@@ -777,10 +784,10 @@ func (r *Runtime) closeNetNS(ctr *Container) error {
return nil
}
-// Tear down a container's CNI network configuration and joins the
+// Tear down a container's network configuration and joins the
// rootless net ns as rootless user
func (r *Runtime) teardownNetwork(ns string, opts types.NetworkOptions) error {
- rootlessCNINS, err := r.GetRootlessCNINetNs(false)
+ rootlessNetNS, err := r.GetRootlessNetNs(false)
if err != nil {
return err
}
@@ -789,13 +796,13 @@ func (r *Runtime) teardownNetwork(ns string, opts types.NetworkOptions) error {
return errors.Wrapf(err, "error tearing down network namespace configuration for container %s", opts.ContainerID)
}
- // rootlessCNINS is nil if we are root
- if rootlessCNINS != nil {
+ // rootlessNetNS is nil if we are root
+ if rootlessNetNS != nil {
// execute the cni setup in the rootless net ns
- err = rootlessCNINS.Do(tearDownPod)
- rootlessCNINS.Lock.Unlock()
+ err = rootlessNetNS.Do(tearDownPod)
+ rootlessNetNS.Lock.Unlock()
if err == nil {
- err = rootlessCNINS.Cleanup(r)
+ err = rootlessNetNS.Cleanup(r)
}
} else {
err = tearDownPod()
@@ -1200,9 +1207,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
ContainerID: c.config.ID,
ContainerName: getCNIPodName(c),
}
- if len(c.config.PortMappings) > 0 {
- opts.PortMappings = ocicniPortsToNetTypesPorts(c.config.PortMappings)
- }
+ opts.PortMappings = c.config.PortMappings
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
@@ -1294,9 +1299,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
ContainerID: c.config.ID,
ContainerName: getCNIPodName(c),
}
- if len(c.config.PortMappings) > 0 {
- opts.PortMappings = ocicniPortsToNetTypesPorts(c.config.PortMappings)
- }
+ opts.PortMappings = c.config.PortMappings
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
@@ -1364,16 +1367,67 @@ func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
return net.Name, nil
}
+// ocicniPortsToNetTypesPorts convert the old port format to the new one
+// while deduplicating ports into ranges
func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping {
+ if len(ports) == 0 {
+ return nil
+ }
+
newPorts := make([]types.PortMapping, 0, len(ports))
- for _, port := range ports {
- newPorts = append(newPorts, types.PortMapping{
- HostIP: port.HostIP,
- HostPort: uint16(port.HostPort),
- ContainerPort: uint16(port.ContainerPort),
- Protocol: port.Protocol,
- Range: 1,
- })
+
+ // first sort the ports
+ sort.Slice(ports, func(i, j int) bool {
+ return compareOCICNIPorts(ports[i], ports[j])
+ })
+
+ // we already check if the slice is empty so we can use the first element
+ currentPort := types.PortMapping{
+ HostIP: ports[0].HostIP,
+ HostPort: uint16(ports[0].HostPort),
+ ContainerPort: uint16(ports[0].ContainerPort),
+ Protocol: ports[0].Protocol,
+ Range: 1,
+ }
+
+ for i := 1; i < len(ports); i++ {
+ if ports[i].HostIP == currentPort.HostIP &&
+ ports[i].Protocol == currentPort.Protocol &&
+ ports[i].HostPort-int32(currentPort.Range) == int32(currentPort.HostPort) &&
+ ports[i].ContainerPort-int32(currentPort.Range) == int32(currentPort.ContainerPort) {
+ currentPort.Range = currentPort.Range + 1
+ } else {
+ newPorts = append(newPorts, currentPort)
+ currentPort = types.PortMapping{
+ HostIP: ports[i].HostIP,
+ HostPort: uint16(ports[i].HostPort),
+ ContainerPort: uint16(ports[i].ContainerPort),
+ Protocol: ports[i].Protocol,
+ Range: 1,
+ }
+ }
}
+ newPorts = append(newPorts, currentPort)
return newPorts
}
+
+// compareOCICNIPorts will sort the ocicni ports by
+// 1) host ip
+// 2) protocol
+// 3) hostPort
+// 4) container port
+func compareOCICNIPorts(i, j types.OCICNIPortMapping) bool {
+ if i.HostIP != j.HostIP {
+ return i.HostIP < j.HostIP
+ }
+
+ if i.Protocol != j.Protocol {
+ return i.Protocol < j.Protocol
+ }
+
+ if i.HostPort != j.HostPort {
+ return i.HostPort < j.HostPort
+ }
+
+ return i.ContainerPort < j.ContainerPort
+}
diff --git a/libpod/networking_linux_test.go b/libpod/networking_linux_test.go
new file mode 100644
index 000000000..06bf05723
--- /dev/null
+++ b/libpod/networking_linux_test.go
@@ -0,0 +1,323 @@
+package libpod
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_ocicniPortsToNetTypesPorts(t *testing.T) {
+ tests := []struct {
+ name string
+ arg []types.OCICNIPortMapping
+ want []types.PortMapping
+ }{
+ {
+ name: "no ports",
+ arg: nil,
+ want: nil,
+ },
+ {
+ name: "empty ports",
+ arg: []types.OCICNIPortMapping{},
+ want: nil,
+ },
+ {
+ name: "single port",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "two separate ports",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 9000,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 9000,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "two ports joined",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ },
+ },
+ {
+ name: "three ports with different container port are not joined",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 79,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8082,
+ ContainerPort: 82,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 79,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 8082,
+ ContainerPort: 82,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "three ports joined (not sorted)",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8082,
+ ContainerPort: 82,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 3,
+ },
+ },
+ },
+ {
+ name: "different protocols ports are not joined",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "udp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "udp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "different host ip ports are not joined",
+ arg: []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ HostIP: "192.168.1.1",
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ HostIP: "192.168.1.2",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ HostIP: "192.168.1.1",
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ Range: 1,
+ HostIP: "192.168.1.2",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ result := ocicniPortsToNetTypesPorts(tt.arg)
+ assert.Equal(t, tt.want, result, "ports do not match")
+ })
+ }
+}
+
+func benchmarkOCICNIPortsToNetTypesPorts(b *testing.B, ports []types.OCICNIPortMapping) {
+ for n := 0; n < b.N; n++ {
+ ocicniPortsToNetTypesPorts(ports)
+ }
+}
+
+func Benchmark_ocicniPortsToNetTypesPortsNoPorts(b *testing.B) {
+ benchmarkOCICNIPortsToNetTypesPorts(b, nil)
+}
+
+func Benchmark_ocicniPortsToNetTypesPorts1(b *testing.B) {
+ benchmarkOCICNIPortsToNetTypesPorts(b, []types.OCICNIPortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ })
+}
+
+func Benchmark_ocicniPortsToNetTypesPorts10(b *testing.B) {
+ ports := make([]types.OCICNIPortMapping, 0, 10)
+ for i := int32(8080); i < 8090; i++ {
+ ports = append(ports, types.OCICNIPortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkOCICNIPortsToNetTypesPorts(b, ports)
+}
+
+func Benchmark_ocicniPortsToNetTypesPorts100(b *testing.B) {
+ ports := make([]types.OCICNIPortMapping, 0, 100)
+ for i := int32(8080); i < 8180; i++ {
+ ports = append(ports, types.OCICNIPortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkOCICNIPortsToNetTypesPorts(b, ports)
+}
+
+func Benchmark_ocicniPortsToNetTypesPorts1k(b *testing.B) {
+ ports := make([]types.OCICNIPortMapping, 0, 1000)
+ for i := int32(8080); i < 9080; i++ {
+ ports = append(ports, types.OCICNIPortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkOCICNIPortsToNetTypesPorts(b, ports)
+}
+
+func Benchmark_ocicniPortsToNetTypesPorts10k(b *testing.B) {
+ ports := make([]types.OCICNIPortMapping, 0, 30000)
+ for i := int32(8080); i < 18080; i++ {
+ ports = append(ports, types.OCICNIPortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkOCICNIPortsToNetTypesPorts(b, ports)
+}
+
+func Benchmark_ocicniPortsToNetTypesPorts1m(b *testing.B) {
+ ports := make([]types.OCICNIPortMapping, 0, 1000000)
+ for j := 0; j < 20; j++ {
+ for i := int32(1); i <= 50000; i++ {
+ ports = append(ports, types.OCICNIPortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ HostIP: fmt.Sprintf("192.168.1.%d", j),
+ })
+ }
+ }
+ b.ResetTimer()
+ benchmarkOCICNIPortsToNetTypesPorts(b, ports)
+}
diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go
index 56e8eca99..760427f22 100644
--- a/libpod/networking_slirp4netns.go
+++ b/libpod/networking_slirp4netns.go
@@ -38,9 +38,9 @@ type slirpFeatures struct {
type slirp4netnsCmdArg struct {
Proto string `json:"proto,omitempty"`
HostAddr string `json:"host_addr"`
- HostPort int32 `json:"host_port"`
+ HostPort uint16 `json:"host_port"`
GuestAddr string `json:"guest_addr"`
- GuestPort int32 `json:"guest_port"`
+ GuestPort uint16 `json:"guest_port"`
}
type slirp4netnsCmd struct {
diff --git a/libpod/oci_util.go b/libpod/oci_util.go
index c1afc0d20..6d99d5836 100644
--- a/libpod/oci_util.go
+++ b/libpod/oci_util.go
@@ -32,93 +32,108 @@ func createUnitName(prefix string, name string) string {
}
// Bind ports to keep them closed on the host
-func bindPorts(ports []types.OCICNIPortMapping) ([]*os.File, error) {
+func bindPorts(ports []types.PortMapping) ([]*os.File, error) {
var files []*os.File
- notifySCTP := false
- for _, i := range ports {
- isV6 := net.ParseIP(i.HostIP).To4() == nil
- if i.HostIP == "" {
+ sctpWarning := true
+ for _, port := range ports {
+ isV6 := net.ParseIP(port.HostIP).To4() == nil
+ if port.HostIP == "" {
isV6 = false
}
- switch i.Protocol {
- case "udp":
- var (
- addr *net.UDPAddr
- err error
- )
- if isV6 {
- addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", i.HostIP, i.HostPort))
- } else {
- addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
- }
- if err != nil {
- return nil, errors.Wrapf(err, "cannot resolve the UDP address")
+ 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 {
+ return files, err
+ }
+ if f != nil {
+ files = append(files, f)
+ }
}
+ }
+ }
+ return files, nil
+}
- proto := "udp4"
- if isV6 {
- proto = "udp6"
- }
- server, err := net.ListenUDP(proto, addr)
- if err != nil {
- return nil, errors.Wrapf(err, "cannot listen on the UDP port")
- }
- f, err := server.File()
- if err != nil {
- return nil, errors.Wrapf(err, "cannot get file for UDP socket")
- }
- files = append(files, f)
- // 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)
- }
+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, errors.Wrapf(err, "cannot resolve the UDP address")
+ }
- case "tcp":
- var (
- addr *net.TCPAddr
- err error
- )
- if isV6 {
- addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", i.HostIP, i.HostPort))
- } else {
- addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
- }
- if err != nil {
- return nil, errors.Wrapf(err, "cannot resolve the TCP address")
- }
+ proto := "udp4"
+ if isV6 {
+ proto = "udp6"
+ }
+ server, err := net.ListenUDP(proto, addr)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot listen on the UDP port")
+ }
+ file, err = server.File()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot get file for UDP socket")
+ }
+ // 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)
+ }
- proto := "tcp4"
- if isV6 {
- proto = "tcp6"
- }
- server, err := net.ListenTCP(proto, addr)
- if err != nil {
- return nil, errors.Wrapf(err, "cannot listen on the TCP port")
- }
- f, err := server.File()
- if err != nil {
- return nil, errors.Wrapf(err, "cannot get file for TCP socket")
- }
- files = append(files, f)
- // 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, errors.Wrapf(err, "cannot resolve the TCP address")
+ }
- case "sctp":
- if !notifySCTP {
- notifySCTP = true
- logrus.Warnf("Port reservation for SCTP is not supported")
- }
- default:
- return nil, fmt.Errorf("unknown protocol %s", i.Protocol)
+ proto := "tcp4"
+ if isV6 {
+ proto = "tcp6"
+ }
+ server, err := net.ListenTCP(proto, addr)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot listen on the TCP port")
+ }
+ file, err = server.File()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot get file for TCP socket")
+ }
+ // 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 files, nil
+ return file, nil
}
func getOCIRuntimeError(runtimeMsg string) error {
diff --git a/libpod/options.go b/libpod/options.go
index f1cf015f8..250b16556 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1051,7 +1051,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.OCICNIPortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption {
+func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
@@ -2084,21 +2084,6 @@ func WithInfraContainer() PodCreateOption {
}
}
-// WithInfraContainerPorts tells the pod to add port bindings to the pause container
-func WithInfraContainerPorts(bindings []nettypes.OCICNIPortMapping, infraSpec *specgen.SpecGenerator) []nettypes.PortMapping {
- bindingSpec := []nettypes.PortMapping{}
- for _, bind := range bindings {
- currBind := nettypes.PortMapping{}
- currBind.ContainerPort = uint16(bind.ContainerPort)
- currBind.HostIP = bind.HostIP
- currBind.HostPort = uint16(bind.HostPort)
- currBind.Protocol = bind.Protocol
- bindingSpec = append(bindingSpec, currBind)
- }
- infraSpec.PortMappings = bindingSpec
- return infraSpec.PortMappings
-}
-
// WithVolatile sets the volatile flag for the container storage.
// The option can potentially cause data loss when used on a container that must survive a machine reboot.
func WithVolatile() CtrCreateOption {
diff --git a/libpod/state_test.go b/libpod/state_test.go
index 4799d7b8d..5c3b0d7f7 100644
--- a/libpod/state_test.go
+++ b/libpod/state_test.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/lock"
+ "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -3705,3 +3706,42 @@ func TestGetContainerConfigNonExistentIDFails(t *testing.T) {
assert.Error(t, err)
})
}
+
+// Test that the state will convert the ports to the new format
+func TestConvertPortMapping(t *testing.T) {
+ runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
+ testCtr, err := getTestCtr1(manager)
+ assert.NoError(t, err)
+
+ ports := testCtr.config.PortMappings
+
+ oldPorts := []types.OCICNIPortMapping{
+ {
+ HostPort: 80,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ HostIP: "192.168.3.3",
+ },
+ {
+ HostPort: 100,
+ ContainerPort: 110,
+ Protocol: "udp",
+ HostIP: "192.168.4.4",
+ },
+ }
+
+ testCtr.config.OldPortMappings = oldPorts
+ testCtr.config.PortMappings = nil
+
+ err = state.AddContainer(testCtr)
+ assert.NoError(t, err)
+
+ retrievedCtr, err := state.Container(testCtr.ID())
+ assert.NoError(t, err)
+
+ // set values to expected ones
+ testCtr.config.PortMappings = ports
+
+ testContainersEqual(t, retrievedCtr, testCtr, true)
+ })
+}
diff --git a/libpod/util.go b/libpod/util.go
index 8f8303ff2..5154a261e 100644
--- a/libpod/util.go
+++ b/libpod/util.go
@@ -295,19 +295,21 @@ func writeHijackHeader(r *http.Request, conn io.Writer) {
}
// Convert OCICNI port bindings into Inspect-formatted port bindings.
-func makeInspectPortBindings(bindings []types.OCICNIPortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort {
+func makeInspectPortBindings(bindings []types.PortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort {
portBindings := make(map[string][]define.InspectHostPort)
for _, port := range bindings {
- key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol)
- hostPorts := portBindings[key]
- if hostPorts == nil {
- hostPorts = []define.InspectHostPort{}
+ protocols := strings.Split(port.Protocol, ",")
+ for _, protocol := range protocols {
+ for i := uint16(0); i < port.Range; i++ {
+ key := fmt.Sprintf("%d/%s", port.ContainerPort+i, protocol)
+ hostPorts := portBindings[key]
+ hostPorts = append(hostPorts, define.InspectHostPort{
+ HostIP: port.HostIP,
+ HostPort: fmt.Sprintf("%d", port.HostPort+i),
+ })
+ portBindings[key] = hostPorts
+ }
}
- hostPorts = append(hostPorts, define.InspectHostPort{
- HostIP: port.HostIP,
- HostPort: fmt.Sprintf("%d", port.HostPort),
- })
- portBindings[key] = hostPorts
}
// add exposed ports without host port information to match docker
for port, protocols := range expose {