summaryrefslogtreecommitdiff
path: root/libpod/network/cni/run_test.go
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2021-07-15 10:29:19 +0200
committerPaul Holzinger <pholzing@redhat.com>2021-08-24 12:28:03 +0200
commitc0b1edd6a4dcad2b89a01975988d186b21b3158e (patch)
treeea500d48f28682ad36f6eb4954717c6629fb68b4 /libpod/network/cni/run_test.go
parente20ec47a59b4ac65d42f3fee7b8b7ec5760ea35d (diff)
downloadpodman-c0b1edd6a4dcad2b89a01975988d186b21b3158e.tar.gz
podman-c0b1edd6a4dcad2b89a01975988d186b21b3158e.tar.bz2
podman-c0b1edd6a4dcad2b89a01975988d186b21b3158e.zip
Network interface
Implement a new network interface to abstract CNI from libpod. The interface is implemented for the CNI backend but in the future we can add more backends. The code is structured in three new packages: - `libpod/network/types`: contains the interface definition and the necessary types for it. - `libpod/network/cni` contains the interface implementation for the CNI backend. - `libpod/network/util` a set of utility functions related to networking. The CNI package uses ginkgo style unit tests. To test Setup/Teardown the test must be run as root. Each test will run in their own namespace to make the test independent from the host environment. New features with the CNI backend: - The default network will be created in memory if it does not exists on disk. - It can set more than one static IP per container network. - Networks are loaded once from disk and only if this interface is used, e.g. for commands such as `podman info` networks are not loaded. This reduces unnecessary disk IO. This commit only adds the interface it is not wired into libpod. This requires a lot of breaking changes which will be done in a followup commit. Once this is integrated into libpod the current network code under `libpod/network` should be removed. Also the dependency on OCICNI should be dropped. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
Diffstat (limited to 'libpod/network/cni/run_test.go')
-rw-r--r--libpod/network/cni/run_test.go1326
1 files changed, 1326 insertions, 0 deletions
diff --git a/libpod/network/cni/run_test.go b/libpod/network/cni/run_test.go
new file mode 100644
index 000000000..32e88ca61
--- /dev/null
+++ b/libpod/network/cni/run_test.go
@@ -0,0 +1,1326 @@
+// +build linux
+
+package cni_test
+
+// The tests have to be run as root.
+// For each test there will be two network namespaces created,
+// netNSTest and netNSContainer. Each test must be run inside
+// netNSTest to prevent leakage in the host netns, therefore
+// it should use the following structure:
+// It("test name", func() {
+// runTest(func() {
+// // add test logic here
+// })
+// })
+
+import (
+ "bytes"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/containernetworking/plugins/pkg/ns"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+ "golang.org/x/sys/unix"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/containers/podman/v3/pkg/netns"
+ "github.com/containers/podman/v3/pkg/rootless"
+ "github.com/containers/storage/pkg/stringid"
+)
+
+var _ = Describe("run CNI", func() {
+ var (
+ libpodNet types.ContainerNetwork
+ cniConfDir string
+ logBuffer bytes.Buffer
+ netNSTest ns.NetNS
+ netNSContainer ns.NetNS
+ )
+ const cniVarDir = "/var/lib/cni"
+
+ // runTest is a helper function to run a test. It ensures that each test
+ // is run in its own netns. It also creates a mountns to mount a tmpfs to /var/lib/cni.
+ runTest := func(run func()) {
+ netNSTest.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ err := os.MkdirAll(cniVarDir, 0755)
+ Expect(err).To(BeNil(), "Failed to create cniVarDir")
+ err = unix.Unshare(unix.CLONE_NEWNS)
+ Expect(err).To(BeNil(), "Failed to create new mountns")
+ err = unix.Mount("tmpfs", cniVarDir, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, "")
+ Expect(err).To(BeNil(), "Failed to mount tmpfs for cniVarDir")
+ defer unix.Unmount(cniVarDir, 0)
+
+ // we have to setup the loopback adapter in this netns to use port forwarding
+ link, err := netlink.LinkByName("lo")
+ Expect(err).To(BeNil(), "Failed to get loopback adapter")
+ err = netlink.LinkSetUp(link)
+ Expect(err).To(BeNil(), "Failed to set loopback adapter up")
+ run()
+ return nil
+ })
+ }
+
+ BeforeEach(func() {
+ // The tests need root privileges.
+ // Technically we could work around that by using user namespaces and
+ // the rootless cni code but this is to much work to get it right for a unit test.
+ if rootless.IsRootless() {
+ Skip("this test needs to be run as root")
+ }
+
+ var err error
+ cniConfDir, err = ioutil.TempDir("", "podman_cni_test")
+ if err != nil {
+ Fail("Failed to create tmpdir")
+ }
+ logBuffer = bytes.Buffer{}
+ logrus.SetOutput(&logBuffer)
+
+ netNSTest, err = netns.NewNS()
+ if err != nil {
+ Fail("Failed to create netns")
+ }
+
+ netNSContainer, err = netns.NewNS()
+ if err != nil {
+ Fail("Failed to create netns")
+ }
+ })
+
+ JustBeforeEach(func() {
+ var err error
+ libpodNet, err = getNetworkInterface(cniConfDir, false)
+ if err != nil {
+ Fail("Failed to create NewCNINetworkInterface")
+ }
+ })
+
+ AfterEach(func() {
+ os.RemoveAll(cniConfDir)
+
+ netns.UnmountNS(netNSTest)
+ netNSTest.Close()
+
+ netns.UnmountNS(netNSContainer)
+ netNSContainer.Close()
+ })
+
+ Context("network setup test", func() {
+
+ It("run with default config", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(defNet))
+ Expect(res[defNet].Interfaces).To(HaveKey(intName))
+ Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
+ Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0."))
+ Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
+ // default network has no dns
+ Expect(res[defNet].DNSServerIPs).To(BeEmpty())
+ Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ })
+ })
+
+ It("run with default config and static ip", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ ip := net.ParseIP("10.88.5.5")
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: intName,
+ StaticIPs: []net.IP{ip},
+ },
+ },
+ },
+ }
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(defNet))
+ Expect(res[defNet].Interfaces).To(HaveKey(intName))
+ Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
+ Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP).To(Equal(ip))
+ Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
+ // default network has no dns
+ Expect(res[defNet].DNSServerIPs).To(BeEmpty())
+ Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ })
+ })
+
+ for _, proto := range []string{"tcp", "udp"} {
+ // copy proto to extra var to keep correct references in the goroutines
+ protocol := proto
+ It("run with exposed ports protocol "+protocol, func() {
+ runTest(func() {
+ testdata := stringid.GenerateNonCryptoID()
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ PortMappings: []types.PortMapping{{
+ Protocol: protocol,
+ HostIP: "127.0.0.1",
+ HostPort: 5000,
+ ContainerPort: 5000,
+ }},
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(defNet))
+ Expect(res[defNet].Interfaces).To(HaveKey(intName))
+ Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
+ Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0."))
+ Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
+ // default network has no dns
+ Expect(res[defNet].DNSServerIPs).To(BeEmpty())
+ Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
+ var wg sync.WaitGroup
+ wg.Add(1)
+ // start a listener in the container ns
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ runNetListener(&wg, protocol, "0.0.0.0", 5000, testdata)
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ conn, err := net.Dial(protocol, "127.0.0.1:5000")
+ Expect(err).To(BeNil())
+ _, err = conn.Write([]byte(testdata))
+ Expect(err).To(BeNil())
+ conn.Close()
+
+ // wait for the listener to finish
+ wg.Wait()
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ })
+ })
+
+ It("run with range ports protocol "+protocol, func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ PortMappings: []types.PortMapping{{
+ Protocol: protocol,
+ HostIP: "127.0.0.1",
+ HostPort: 5001,
+ ContainerPort: 5000,
+ Range: 3,
+ }},
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(defNet))
+ Expect(res[defNet].Interfaces).To(HaveKey(intName))
+ Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
+ containerIP := res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()
+ Expect(containerIP).To(ContainSubstring("10.88.0."))
+ Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
+ // default network has no dns
+ Expect(res[defNet].DNSServerIPs).To(BeEmpty())
+ Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
+
+ // loop over all ports
+ for p := 5001; p < 5004; p++ {
+ port := p
+ var wg sync.WaitGroup
+ wg.Add(1)
+ testdata := stringid.GenerateNonCryptoID()
+ // start a listener in the container ns
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ runNetListener(&wg, protocol, containerIP, port-1, testdata)
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ conn, err := net.Dial(protocol, net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
+ Expect(err).To(BeNil())
+ _, err = conn.Write([]byte(testdata))
+ Expect(err).To(BeNil())
+ conn.Close()
+
+ // wait for the listener to finish
+ wg.Wait()
+ }
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ })
+ })
+ }
+
+ It("run with comma separated port protocol", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ PortMappings: []types.PortMapping{{
+ Protocol: "tcp,udp",
+ HostIP: "127.0.0.1",
+ HostPort: 5000,
+ ContainerPort: 5000,
+ }},
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(defNet))
+ Expect(res[defNet].Interfaces).To(HaveKey(intName))
+ Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
+ Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0."))
+ Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
+
+ for _, proto := range []string{"tcp", "udp"} {
+ // copy proto to extra var to keep correct references in the goroutines
+ protocol := proto
+
+ testdata := stringid.GenerateNonCryptoID()
+ var wg sync.WaitGroup
+ wg.Add(1)
+ // start tcp listener in the container ns
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ runNetListener(&wg, protocol, "0.0.0.0", 5000, testdata)
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ conn, err := net.Dial(protocol, "127.0.0.1:5000")
+ Expect(err).To(BeNil())
+ _, err = conn.Write([]byte(testdata))
+ Expect(err).To(BeNil())
+ conn.Close()
+
+ // wait for the listener to finish
+ wg.Wait()
+ }
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ })
+ })
+
+ It("call setup twice", func() {
+ runTest(func() {
+ network := types.Network{}
+ network1, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ intName1 := "eth0"
+ netName1 := network1.Name
+
+ containerID := stringid.GenerateNonCryptoID()
+
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: containerID,
+ Networks: map[string]types.PerNetworkOptions{
+ netName1: {
+ InterfaceName: intName1,
+ },
+ },
+ },
+ }
+
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+
+ Expect(res).To(HaveKey(netName1))
+ Expect(res[netName1].Interfaces).To(HaveKey(intName1))
+ Expect(res[netName1].Interfaces[intName1].Networks).To(HaveLen(1))
+ ipInt1 := res[netName1].Interfaces[intName1].Networks[0].Subnet.IP
+ Expect(ipInt1).ToNot(BeEmpty())
+ macInt1 := res[netName1].Interfaces[intName1].MacAddress
+ Expect(macInt1).To(HaveLen(6))
+
+ // check in the container namespace if the settings are applied
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ i, err := net.InterfaceByName(intName1)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(intName1))
+ Expect(i.HardwareAddr).To(Equal(macInt1))
+ addrs, err := i.Addrs()
+ Expect(err).To(BeNil())
+ subnet := &net.IPNet{
+ IP: ipInt1,
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet))
+
+ // check loopback adapter
+ i, err = net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ network = types.Network{}
+ network2, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ intName2 := "eth1"
+ netName2 := network2.Name
+
+ setupOpts.Networks = map[string]types.PerNetworkOptions{
+ netName2: {
+ InterfaceName: intName2,
+ },
+ }
+
+ res, err = libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+
+ Expect(res).To(HaveKey(netName2))
+ Expect(res[netName2].Interfaces).To(HaveKey(intName2))
+ Expect(res[netName2].Interfaces[intName2].Networks).To(HaveLen(1))
+ ipInt2 := res[netName2].Interfaces[intName2].Networks[0].Subnet.IP
+ Expect(ipInt2).ToNot(BeEmpty())
+ macInt2 := res[netName2].Interfaces[intName2].MacAddress
+ Expect(macInt2).To(HaveLen(6))
+
+ // check in the container namespace if the settings are applied
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ i, err := net.InterfaceByName(intName1)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(intName1))
+ Expect(i.HardwareAddr).To(Equal(macInt1))
+ addrs, err := i.Addrs()
+ Expect(err).To(BeNil())
+ subnet := &net.IPNet{
+ IP: ipInt1,
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet))
+
+ i, err = net.InterfaceByName(intName2)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(intName2))
+ Expect(i.HardwareAddr).To(Equal(macInt2))
+ addrs, err = i.Addrs()
+ Expect(err).To(BeNil())
+ subnet = &net.IPNet{
+ IP: ipInt2,
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet))
+
+ // check loopback adapter
+ i, err = net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ teatdownOpts := types.TeardownOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: containerID,
+ Networks: map[string]types.PerNetworkOptions{
+ netName1: {
+ InterfaceName: intName1,
+ },
+ netName2: {
+ InterfaceName: intName2,
+ },
+ },
+ },
+ }
+
+ err = libpodNet.Teardown(netNSContainer.Path(), teatdownOpts)
+ Expect(err).To(BeNil())
+ logString := logBuffer.String()
+ Expect(logString).To(BeEmpty())
+
+ // check in the container namespace that the interface is removed
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ _, err := net.InterfaceByName(intName1)
+ Expect(err).To(HaveOccurred())
+ _, err = net.InterfaceByName(intName2)
+ Expect(err).To(HaveOccurred())
+
+ // check that only the loopback adapter is left
+ ints, err := net.Interfaces()
+ Expect(err).To(BeNil())
+ Expect(ints).To(HaveLen(1))
+ Expect(ints[0].Name).To(Equal("lo"))
+ Expect(ints[0].Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(ints[0].Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ err = libpodNet.NetworkRemove(netName1)
+ Expect(err).To(BeNil())
+ err = libpodNet.NetworkRemove(netName2)
+ Expect(err).To(BeNil())
+
+ // check that the interfaces are removed in the host ns
+ _, err = net.InterfaceByName(network1.NetworkInterface)
+ Expect(err).To(HaveOccurred())
+ _, err = net.InterfaceByName(network2.NetworkInterface)
+ Expect(err).To(HaveOccurred())
+ })
+ })
+
+ It("setup two networks with one setup call", func() {
+ runTest(func() {
+ subnet1, _ := types.ParseCIDR("192.168.0.0/24")
+ subnet2, _ := types.ParseCIDR("192.168.1.0/24")
+ network := types.Network{
+ Subnets: []types.Subnet{
+ {Subnet: subnet1},
+ },
+ }
+ network1, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ network = types.Network{
+ Subnets: []types.Subnet{
+ {Subnet: subnet2},
+ },
+ }
+ network2, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ intName1 := "eth0"
+ intName2 := "eth1"
+ netName1 := network1.Name
+ netName2 := network2.Name
+
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ netName1: {
+ InterfaceName: intName1,
+ },
+ netName2: {
+ InterfaceName: intName2,
+ },
+ },
+ },
+ }
+
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(2))
+
+ Expect(res).To(HaveKey(netName1))
+ Expect(res[netName1].Interfaces).To(HaveKey(intName1))
+ Expect(res[netName1].Interfaces[intName1].Networks).To(HaveLen(1))
+ ipInt1 := res[netName1].Interfaces[intName1].Networks[0].Subnet.IP
+ Expect(ipInt1.String()).To(ContainSubstring("192.168.0."))
+ macInt1 := res[netName1].Interfaces[intName1].MacAddress
+ Expect(macInt1).To(HaveLen(6))
+
+ Expect(res).To(HaveKey(netName2))
+ Expect(res[netName2].Interfaces).To(HaveKey(intName2))
+ Expect(res[netName2].Interfaces[intName2].Networks).To(HaveLen(1))
+ ipInt2 := res[netName2].Interfaces[intName2].Networks[0].Subnet.IP
+ Expect(ipInt2.String()).To(ContainSubstring("192.168.1."))
+ macInt2 := res[netName2].Interfaces[intName2].MacAddress
+ Expect(macInt2).To(HaveLen(6))
+
+ // default network has no dns
+ Expect(res[netName1].DNSServerIPs).To(BeEmpty())
+ Expect(res[netName1].DNSSearchDomains).To(BeEmpty())
+
+ // check in the container namespace if the settings are applied
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ i, err := net.InterfaceByName(intName1)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(intName1))
+ Expect(i.HardwareAddr).To(Equal(macInt1))
+ addrs, err := i.Addrs()
+ Expect(err).To(BeNil())
+ subnet := &net.IPNet{
+ IP: ipInt1,
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet))
+
+ i, err = net.InterfaceByName(intName2)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(intName2))
+ Expect(i.HardwareAddr).To(Equal(macInt2))
+ addrs, err = i.Addrs()
+ Expect(err).To(BeNil())
+ subnet = &net.IPNet{
+ IP: ipInt2,
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet))
+
+ // check loopback adapter
+ i, err = net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ logString := logBuffer.String()
+ Expect(logString).To(BeEmpty())
+
+ // check in the container namespace that the interface is removed
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ _, err := net.InterfaceByName(intName1)
+ Expect(err).To(HaveOccurred())
+ _, err = net.InterfaceByName(intName2)
+ Expect(err).To(HaveOccurred())
+
+ // check that only the loopback adapter is left
+ ints, err := net.Interfaces()
+ Expect(err).To(BeNil())
+ Expect(ints).To(HaveLen(1))
+ Expect(ints[0].Name).To(Equal("lo"))
+ Expect(ints[0].Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(ints[0].Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+
+ return nil
+ })
+ Expect(err).To(BeNil())
+ })
+
+ })
+
+ It("dual stack network with static ips", func() {
+ // Version checks for cni plugins are not possible, the plugins do not output
+ // version information and using the package manager does not work across distros.
+ // Fedora has the right version so we use this for now.
+ SkipIfNotFedora("requires cni plugins 1.0.0 or newer for multiple static ips")
+ runTest(func() {
+ subnet1, _ := types.ParseCIDR("192.168.0.0/24")
+ subnet2, _ := types.ParseCIDR("fd41:0a75:2ca0:48a9::/64")
+ network := types.Network{
+ Subnets: []types.Subnet{
+ {Subnet: subnet1}, {Subnet: subnet2},
+ },
+ }
+ network1, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ mac, _ := net.ParseMAC("40:15:2f:d8:42:36")
+ interfaceName := "eth0"
+
+ ip1 := net.ParseIP("192.168.0.5")
+ ip2 := net.ParseIP("fd41:0a75:2ca0:48a9::5")
+
+ netName := network1.Name
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerName: "mycon",
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ netName: {
+ InterfaceName: interfaceName,
+ StaticIPs: []net.IP{ip1, ip2},
+ StaticMAC: mac,
+ },
+ },
+ },
+ }
+
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(netName))
+ Expect(res[netName].Interfaces).To(HaveKey(interfaceName))
+ Expect(res[netName].Interfaces[interfaceName].Networks).To(HaveLen(2))
+ Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.IP.String()).To(Equal(ip1.String()))
+ Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.Mask).To(Equal(subnet1.Mask))
+ Expect(res[netName].Interfaces[interfaceName].Networks[0].Gateway).To(Equal(net.ParseIP("192.168.0.1")))
+ Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.IP.String()).To(Equal(ip2.String()))
+ Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.Mask).To(Equal(subnet2.Mask))
+ Expect(res[netName].Interfaces[interfaceName].Networks[1].Gateway).To(Equal(net.ParseIP("fd41:0a75:2ca0:48a9::1")))
+ Expect(res[netName].Interfaces[interfaceName].MacAddress).To(Equal(mac))
+ // default network has no dns
+ Expect(res[netName].DNSServerIPs).To(BeEmpty())
+ Expect(res[netName].DNSSearchDomains).To(BeEmpty())
+
+ // check in the container namespace if the settings are applied
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ i, err := net.InterfaceByName(interfaceName)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(interfaceName))
+ Expect(i.HardwareAddr).To(Equal(mac))
+ addrs, err := i.Addrs()
+ Expect(err).To(BeNil())
+ subnet1 := &net.IPNet{
+ IP: ip1,
+ Mask: net.CIDRMask(24, 32),
+ }
+ subnet2 := &net.IPNet{
+ IP: ip2,
+ Mask: net.CIDRMask(64, 128),
+ }
+ Expect(addrs).To(ContainElements(subnet1, subnet2))
+
+ // check loopback adapter
+ i, err = net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ logString := logBuffer.String()
+ Expect(logString).To(BeEmpty())
+
+ // check in the container namespace that the interface is removed
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ _, err := net.InterfaceByName(interfaceName)
+ Expect(err).To(HaveOccurred())
+
+ // check that only the loopback adapter is left
+ ints, err := net.Interfaces()
+ Expect(err).To(BeNil())
+ Expect(ints).To(HaveLen(1))
+ Expect(ints[0].Name).To(Equal("lo"))
+ Expect(ints[0].Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(ints[0].Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+
+ return nil
+ })
+ Expect(err).To(BeNil())
+ })
+ })
+
+ It("CNI_ARGS from environment variable", func() {
+ runTest(func() {
+ subnet1, _ := types.ParseCIDR("172.16.1.0/24")
+ ip := "172.16.1.5"
+ network := types.Network{
+ Subnets: []types.Subnet{
+ {Subnet: subnet1},
+ },
+ }
+ network1, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+ netName := network1.Name
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ netName: {
+ InterfaceName: intName,
+ },
+ },
+ },
+ }
+
+ os.Setenv("CNI_ARGS", "IP="+ip)
+ defer os.Unsetenv("CNI_ARGS")
+
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(netName))
+ Expect(res[netName].Interfaces).To(HaveKey(intName))
+ Expect(res[netName].Interfaces[intName].Networks).To(HaveLen(1))
+ Expect(res[netName].Interfaces[intName].Networks[0].Subnet.IP.String()).To(Equal(ip))
+ Expect(res[netName].Interfaces[intName].Networks[0].Subnet.Mask).To(Equal(net.CIDRMask(24, 32)))
+
+ // check in the container namespace if the settings are applied
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ i, err := net.InterfaceByName(intName)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(intName))
+ addrs, err := i.Addrs()
+ Expect(err).To(BeNil())
+ subnet := &net.IPNet{
+ IP: net.ParseIP(ip),
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet))
+
+ // check loopback adapter
+ i, err = net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+ return nil
+ })
+ Expect(err).To(BeNil())
+ })
+ })
+ })
+
+ Context("network setup test with networks from disk", func() {
+
+ BeforeEach(func() {
+ dir := "testfiles/valid"
+ files, err := ioutil.ReadDir(dir)
+ if err != nil {
+ Fail("Failed to read test directory")
+ }
+ for _, file := range files {
+ filename := file.Name()
+ data, err := ioutil.ReadFile(filepath.Join(dir, filename))
+ if err != nil {
+ Fail("Failed to copy test files")
+ }
+ err = ioutil.WriteFile(filepath.Join(cniConfDir, filename), data, 0700)
+ if err != nil {
+ Fail("Failed to copy test files")
+ }
+ }
+ })
+
+ It("dualstack setup with static ip and dns", func() {
+ SkipIfNoDnsname()
+ // Version checks for cni plugins are not possible, the plugins do not output
+ // version information and using the package manager does not work across distros.
+ // Fedora has the right version so we use this for now.
+ SkipIfNotFedora("requires cni plugins 1.0.0 or newer for multiple static ips")
+ runTest(func() {
+ interfaceName := "eth0"
+
+ ip1 := net.ParseIP("fd10:88:a::11")
+ ip2 := net.ParseIP("10.89.19.15")
+
+ containerName := "myname"
+ aliases := []string{"aliasname"}
+
+ netName := "dualstack"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ ContainerName: containerName,
+ Networks: map[string]types.PerNetworkOptions{
+ netName: {
+ InterfaceName: interfaceName,
+ StaticIPs: []net.IP{ip1, ip2},
+ Aliases: aliases,
+ },
+ },
+ },
+ }
+
+ network, err := libpodNet.NetworkInspect(netName)
+ Expect(err).To(BeNil())
+ Expect(network.Name).To(Equal(netName))
+ Expect(network.DNSEnabled).To(BeTrue())
+ Expect(network.Subnets).To(HaveLen(2))
+ gw1 := network.Subnets[0].Gateway
+ Expect(gw1).To(HaveLen(16))
+ mask1 := network.Subnets[0].Subnet.Mask
+ Expect(mask1).To(HaveLen(16))
+ gw2 := network.Subnets[1].Gateway
+ Expect(gw2).To(HaveLen(4))
+ mask2 := network.Subnets[1].Subnet.Mask
+ Expect(mask2).To(HaveLen(4))
+
+ // because this net has dns we should always teardown otherwise we leak a dnsmasq process
+ defer libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(BeNil())
+ Expect(res).To(HaveLen(1))
+ Expect(res).To(HaveKey(netName))
+ Expect(res[netName].Interfaces).To(HaveKey(interfaceName))
+ Expect(res[netName].Interfaces[interfaceName].Networks).To(HaveLen(2))
+ Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.IP.String()).To(Equal(ip1.String()))
+ Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.Mask).To(Equal(mask1))
+ Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.IP.String()).To(Equal(ip2.String()))
+ Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.Mask).To(Equal(mask2))
+ // dualstack network dns
+ Expect(res[netName].DNSServerIPs).To(HaveLen(2))
+ Expect(res[netName].DNSSearchDomains).To(HaveLen(1))
+ Expect(res[netName].DNSSearchDomains).To(ConsistOf("dns.podman"))
+
+ // check in the container namespace if the settings are applied
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ i, err := net.InterfaceByName(interfaceName)
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal(interfaceName))
+ addrs, err := i.Addrs()
+ Expect(err).To(BeNil())
+ subnet1 := &net.IPNet{
+ IP: ip1,
+ Mask: net.CIDRMask(64, 128),
+ }
+ subnet2 := &net.IPNet{
+ IP: ip2,
+ Mask: net.CIDRMask(24, 32),
+ }
+ Expect(addrs).To(ContainElements(subnet1, subnet2))
+
+ // check loopback adapter
+ i, err = net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+
+ return nil
+ })
+ Expect(err).To(BeNil())
+
+ err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
+ Expect(err).To(BeNil())
+ logString := logBuffer.String()
+ Expect(logString).To(BeEmpty())
+
+ // check in the container namespace that the interface is removed
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ _, err := net.InterfaceByName(interfaceName)
+ Expect(err).To(HaveOccurred())
+
+ // check that only the loopback adapter is left
+ ints, err := net.Interfaces()
+ Expect(err).To(BeNil())
+ Expect(ints).To(HaveLen(1))
+ Expect(ints[0].Name).To(Equal("lo"))
+ Expect(ints[0].Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(ints[0].Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+
+ return nil
+ })
+ Expect(err).To(BeNil())
+ })
+ })
+
+ })
+
+ Context("invalid network setup test", func() {
+
+ It("static ip not in subnet", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ ip := "1.1.1.1"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: intName,
+ StaticIPs: []net.IP{net.ParseIP(ip)},
+ },
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("requested static ip %s not in any subnet on network %s", ip, defNet))
+ })
+ })
+
+ It("setup without namespace path", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: intName,
+ },
+ },
+ },
+ }
+ _, err := libpodNet.Setup("", setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("namespacePath is empty"))
+ })
+ })
+
+ It("setup with invalid namespace path", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: intName,
+ },
+ },
+ },
+ }
+ _, err := libpodNet.Setup("some path", setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring(`"some path": no such file or directory`))
+ })
+ })
+
+ It("setup without container ID", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: "",
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: intName,
+ },
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("ContainerID is empty"))
+ })
+ })
+
+ It("setup with aliases but dns disabled", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: intName,
+ Aliases: []string{"somealias"},
+ },
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("cannot set aliases on a network without dns enabled"))
+ })
+ })
+
+ It("setup without networks", func() {
+ runTest(func() {
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("must specify at least one network"))
+ })
+ })
+
+ It("setup without interface name", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {
+ InterfaceName: "",
+ },
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("interface name on network %s is empty", defNet))
+ })
+ })
+
+ It("setup does teardown on failure", func() {
+ runTest(func() {
+ subnet1, _ := types.ParseCIDR("192.168.0.0/24")
+ network := types.Network{
+ Subnets: []types.Subnet{
+ {Subnet: subnet1},
+ },
+ }
+ network1, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ subnet2, _ := types.ParseCIDR("192.168.1.0/31")
+ network = types.Network{
+ Subnets: []types.Subnet{
+ {Subnet: subnet2},
+ },
+ }
+ network2, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ intName1 := "eth0"
+ intName2 := "eth1"
+ netName1 := network1.Name
+ netName2 := network2.Name
+
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ netName1: {
+ InterfaceName: intName1,
+ },
+ netName2: {
+ InterfaceName: intName2,
+ },
+ },
+ },
+ }
+ _, err = libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("Network 192.168.1.0/31 too small to allocate from"))
+ // Note: we call teardown on the failing net and log the error, it should be the same.
+ logString := logBuffer.String()
+ Expect(logString).To(ContainSubstring("Network 192.168.1.0/31 too small to allocate from"))
+
+ // check in the container namespace that no interface is there
+ err = netNSContainer.Do(func(_ ns.NetNS) error {
+ defer GinkgoRecover()
+ _, err := net.InterfaceByName(intName1)
+ Expect(err).To(HaveOccurred())
+
+ // Note: We can check if intName2 is removed because
+ // the cni plugin fails before it removes the interface
+
+ // check loopback adapter
+ i, err := net.InterfaceByName("lo")
+ Expect(err).To(BeNil())
+ Expect(i.Name).To(Equal("lo"))
+ Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
+ Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
+ return nil
+ })
+ Expect(err).To(BeNil())
+ })
+ })
+
+ It("setup with exposed invalid port protocol", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ PortMappings: []types.PortMapping{{
+ Protocol: "someproto",
+ HostIP: "127.0.0.1",
+ HostPort: 5000,
+ ContainerPort: 5000,
+ }},
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("unknown port protocol someproto"))
+ })
+ })
+
+ It("setup with exposed empty port protocol", func() {
+ runTest(func() {
+ defNet := types.DefaultNetworkName
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ PortMappings: []types.PortMapping{{
+ Protocol: "",
+ HostIP: "127.0.0.1",
+ HostPort: 5000,
+ ContainerPort: 5000,
+ }},
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("port protocol should not be empty"))
+ })
+ })
+
+ It("setup with unknown network", func() {
+ runTest(func() {
+ defNet := "somenet"
+ intName := "eth0"
+ setupOpts := types.SetupOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ defNet: {InterfaceName: intName},
+ },
+ },
+ }
+ _, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("network somenet: network not found"))
+ })
+ })
+
+ It("teardown with unknown network", func() {
+ runTest(func() {
+ interfaceName := "eth0"
+ netName := "somenet"
+ teardownOpts := types.TeardownOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ netName: {
+ InterfaceName: interfaceName,
+ },
+ },
+ },
+ }
+
+ err := libpodNet.Teardown(netNSContainer.Path(), teardownOpts)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("network somenet: network not found"))
+ logString := logBuffer.String()
+ Expect(logString).To(ContainSubstring("failed to load cached network config"))
+ })
+ })
+
+ It("teardown on not connected network", func() {
+ runTest(func() {
+ network := types.Network{}
+ network1, err := libpodNet.NetworkCreate(network)
+ Expect(err).To(BeNil())
+
+ interfaceName := "eth0"
+ netName := network1.Name
+ teardownOpts := types.TeardownOptions{
+ NetworkOptions: types.NetworkOptions{
+ ContainerID: stringid.GenerateNonCryptoID(),
+ Networks: map[string]types.PerNetworkOptions{
+ netName: {
+ InterfaceName: interfaceName,
+ },
+ },
+ },
+ }
+
+ // Most CNI plugins do not error on teardown when there is nothing to do.
+ err = libpodNet.Teardown(netNSContainer.Path(), teardownOpts)
+ Expect(err).To(BeNil())
+ logString := logBuffer.String()
+ Expect(logString).To(ContainSubstring("failed to load cached network config"))
+ })
+ })
+ })
+})
+
+func runNetListener(wg *sync.WaitGroup, protocol, ip string, port int, expectedData string) {
+ switch protocol {
+ case "tcp":
+ ln, err := net.Listen(protocol, net.JoinHostPort(ip, strconv.Itoa(port)))
+ Expect(err).To(BeNil())
+ // make sure to read in a separate goroutine to not block
+ go func() {
+ defer GinkgoRecover()
+ defer wg.Done()
+ conn, err := ln.Accept()
+ Expect(err).To(BeNil())
+ conn.SetDeadline(time.Now().Add(1 * time.Second))
+ data, err := ioutil.ReadAll(conn)
+ Expect(err).To(BeNil())
+ Expect(string(data)).To(Equal(expectedData))
+ conn.Close()
+ ln.Close()
+ }()
+ case "udp":
+ conn, err := net.ListenUDP("udp", &net.UDPAddr{
+ IP: net.ParseIP(ip),
+ Port: port,
+ })
+ Expect(err).To(BeNil())
+ conn.SetDeadline(time.Now().Add(1 * time.Second))
+ go func() {
+ defer GinkgoRecover()
+ defer wg.Done()
+ data := make([]byte, len(expectedData))
+ i, err := conn.Read(data)
+ Expect(err).To(BeNil())
+ Expect(i).To(Equal(len(expectedData)))
+ Expect(string(data)).To(Equal(expectedData))
+ conn.Close()
+ }()
+ default:
+ Fail("unsupported protocol")
+ }
+}