// +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")
	}
}
