podman/libpod/network/cni/run_test.go

1330 lines
42 KiB
Go

// +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())
// reload the interface so the networks are reload from disk
libpodNet, err := getNetworkInterface(cniConfDir, false)
Expect(err).To(BeNil())
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((net.HardwareAddr)(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(net.HardwareAddr(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(net.HardwareAddr(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(net.HardwareAddr(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(net.HardwareAddr(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: types.HardwareAddr(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(types.HardwareAddr(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())
})
})
It("setup with aliases but dns disabled should work", 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).ToNot(HaveOccurred())
})
})
})
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 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")
}
}