diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state_internal.go | 9 | ||||
-rw-r--r-- | libpod/common_test.go | 4 | ||||
-rw-r--r-- | libpod/container.go | 2 | ||||
-rw-r--r-- | libpod/container_config.go | 12 | ||||
-rw-r--r-- | libpod/container_internal.go | 13 | ||||
-rw-r--r-- | libpod/kube.go | 51 | ||||
-rw-r--r-- | libpod/network/types/network.go | 1 | ||||
-rw-r--r-- | libpod/networking_linux.go | 330 | ||||
-rw-r--r-- | libpod/networking_linux_test.go | 323 | ||||
-rw-r--r-- | libpod/networking_slirp4netns.go | 4 | ||||
-rw-r--r-- | libpod/oci_util.go | 169 | ||||
-rw-r--r-- | libpod/options.go | 17 | ||||
-rw-r--r-- | libpod/state_test.go | 40 | ||||
-rw-r--r-- | libpod/util.go | 22 |
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 { |