Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set interface name to the network_interface name for macvlan and ipvlan networks #21446

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions libpod/networking_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro

// check if network exists and if the input is an ID we get the name
// CNI and netavark and the libpod db only uses names so it is important that we only use the name
netName, err = c.runtime.normalizeNetworkName(netName)
netName, _, err = c.runtime.normalizeNetworkName(netName)
if err != nil {
return err
}
Expand Down Expand Up @@ -467,7 +467,8 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe

// check if network exists and if the input is an ID we get the name
// CNI and netavark and the libpod db only uses names so it is important that we only use the name
netName, err = c.runtime.normalizeNetworkName(netName)
var nicName string
netName, nicName, err = c.runtime.normalizeNetworkName(netName)
if err != nil {
return err
}
Expand All @@ -481,6 +482,13 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe

netOpts.Aliases = append(netOpts.Aliases, getExtraNetworkAliases(c)...)

// check whether interface is to be named as the network_interface
// when name left unspecified
if netOpts.InterfaceName == "" {
netOpts.InterfaceName = nicName
}

// set default interface name
if netOpts.InterfaceName == "" {
netOpts.InterfaceName = getFreeInterfaceName(networks)
if netOpts.InterfaceName == "" {
Expand Down Expand Up @@ -632,14 +640,24 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts ty
return ctr.NetworkConnect(nameOrID, netName, netOpts)
}

// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
// normalizeNetworkName takes a network name, a partial or a full network ID and
// returns: 1) the network name and 2) the network_interface name for macvlan
// and ipvlan drivers if the naming pattern is "device" defined in the
// containers.conf file. Else, "".
// If the network is not found an error is returned.
func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
func (r *Runtime) normalizeNetworkName(nameOrID string) (string, string, error) {
net, err := r.network.NetworkInspect(nameOrID)
if err != nil {
return "", err
return "", "", err
}
return net.Name, nil

netIface := ""
namingPattern := r.config.Containers.InterfaceName
if namingPattern == "device" && (net.Driver == types.MacVLANNetworkDriver || net.Driver == types.IPVLANNetworkDriver) {
netIface = net.NetworkInterface
}

return net.Name, netIface, nil
}

// ocicniPortsToNetTypesPorts convert the old port format to the new one
Expand Down
11 changes: 9 additions & 2 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,18 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
}
i := 0
for nameOrID, opts := range ctr.config.Networks {
netName, err := r.normalizeNetworkName(nameOrID)
netName, nicName, err := r.normalizeNetworkName(nameOrID)
if err != nil {
return nil, err
}
// assign interface name if empty

// check whether interface is to be named as the network_interface
// when name left unspecified
if opts.InterfaceName == "" {
opts.InterfaceName = nicName
}

// assign default interface name if empty
if opts.InterfaceName == "" {
for i < 100000 {
ifName := fmt.Sprintf("eth%d", i)
Expand Down
280 changes: 280 additions & 0 deletions test/e2e/container_iface_name_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package integration

import (
"os"
"path/filepath"

. "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func isDebianRunc(pTest *PodmanTestIntegration) bool {
info := GetHostDistributionInfo()
if info.Distribution == "debian" && pTest.OCIRuntime == "runc" {
return true
}

return false
}

func createNetworkDevice(name string) {
session := SystemExec("ip", []string{"link", "add", name, "type", "bridge"})
session.WaitWithDefaultTimeout()
vikas-goel marked this conversation as resolved.
Show resolved Hide resolved
Expect(session).Should(ExitCleanly())
}

func deleteNetworkDevice(name string) {
session := SystemExec("ip", []string{"link", "delete", name})
session.WaitWithDefaultTimeout()
vikas-goel marked this conversation as resolved.
Show resolved Hide resolved
Expect(session).Should(ExitCleanly())
}

func createContainersConfFileWithDeviceIfaceName(pTest *PodmanTestIntegration) {
configPath := filepath.Join(pTest.TempDir, "containers.conf")
containersConf := []byte("[containers]\ninterface_name = \"device\"\n")
err := os.WriteFile(configPath, containersConf, os.ModePerm)
Expect(err).ToNot(HaveOccurred())

// Set custom containers.conf file
os.Setenv("CONTAINERS_CONF_OVERRIDE", configPath)
if IsRemote() {
pTest.RestartRemoteService()
}
}

var _ = Describe("Podman container interface name", func() {

It("podman container interface name for bridge network", func() {
// Assert that the network interface name inside container for
// bridge network is ethX regardless of interface_name setting
// in the containers.conf file.

netName1 := createNetworkName("bridge")
netName2 := createNetworkName("bridge")

defer podmanTest.removeNetwork(netName1)
nc1 := podmanTest.Podman([]string{"network", "create", netName1})
nc1.WaitWithDefaultTimeout()
Expect(nc1).Should(ExitCleanly())

defer podmanTest.removeNetwork(netName2)
nc2 := podmanTest.Podman([]string{"network", "create", netName2})
nc2.WaitWithDefaultTimeout()
Expect(nc2).Should(ExitCleanly())

for _, override := range []bool{false, true} {
if override {
createContainersConfFileWithDeviceIfaceName(podmanTest)
}

ctr := podmanTest.Podman([]string{"run", "-d", "--network", netName1, "--name", "test", ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr).Should(ExitCleanly())

exec1 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", "eth0"})
exec1.WaitWithDefaultTimeout()
Expect(exec1).Should(ExitCleanly())
Expect(exec1.OutputToString()).Should(ContainSubstring("eth0"))

conn := podmanTest.Podman([]string{"network", "connect", netName2, "test"})
conn.WaitWithDefaultTimeout()
Expect(conn).Should(ExitCleanly())

exec2 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", "eth1"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(ExitCleanly())
Expect(exec2.OutputToString()).Should(ContainSubstring("eth1"))

rm := podmanTest.Podman([]string{"rm", "--time=0", "-f", "test"})
rm.WaitWithDefaultTimeout()
Expect(rm).Should(ExitCleanly())
vikas-goel marked this conversation as resolved.
Show resolved Hide resolved
}
})

It("podman container interface name for macvlan/ipvlan network with no parent", func() {
// Assert that the network interface name inside container for
// macvlan/ipvlan network with no parent interface is ethX
// regardless of interface_name setting in the containers.conf
// file.

for _, override := range []bool{false, true} {
if override {
createContainersConfFileWithDeviceIfaceName(podmanTest)
}

for _, driverType := range []string{"macvlan", "ipvlan"} {
if driverType == "ipvlan" && isDebianRunc(podmanTest) {
GinkgoWriter.Println("FIXME: Fails with netavark < 1.10. Re-enable once Debian gets an update")
continue
}

netName1 := createNetworkName(driverType)
netName2 := createNetworkName(driverType)

// There is no nic created by the macvlan/ipvlan driver.
defer podmanTest.removeNetwork(netName1)
nc1 := podmanTest.Podman([]string{"network", "create", "-d", driverType, "--subnet", "10.10.0.0/24", netName1})
nc1.WaitWithDefaultTimeout()
Expect(nc1).Should(ExitCleanly())

ctr := podmanTest.Podman([]string{"run", "-d", "--network", netName1, "--name", "test", ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr).Should(ExitCleanly())

exec1 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", "eth0"})
exec1.WaitWithDefaultTimeout()
Expect(exec1).Should(ExitCleanly())
Expect(exec1.OutputToString()).Should(ContainSubstring("eth0"))

defer podmanTest.removeNetwork(netName2)
nc2 := podmanTest.Podman([]string{"network", "create", "-d", driverType, "--subnet", "10.25.40.0/24", netName2})
nc2.WaitWithDefaultTimeout()
Expect(nc2).Should(ExitCleanly())

conn := podmanTest.Podman([]string{"network", "connect", netName2, "test"})
conn.WaitWithDefaultTimeout()
Expect(conn).Should(ExitCleanly())

exec2 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", "eth1"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(ExitCleanly())
Expect(exec2.OutputToString()).Should(ContainSubstring("eth1"))

rm := podmanTest.Podman([]string{"rm", "--time=0", "-f", "test"})
rm.WaitWithDefaultTimeout()
Expect(rm).Should(ExitCleanly())
}
}
})

It("podman container interface name with default scheme for macvlan/ipvlan network with parent", func() {
// Assert that the network interface name inside container for
// macvlan/ipvlan network, created with a specific parent
// interface, continues to be ethX when interface_name in the
// containers.conf file is set to default value, i.e., "".

SkipIfRootless("cannot create network device in rootless mode.")

for _, driverType := range []string{"macvlan", "ipvlan"} {
if driverType == "ipvlan" && isDebianRunc(podmanTest) {
GinkgoWriter.Println("FIXME: Fails with netavark < 1.10. Re-enable once Debian gets an update")
continue
}

// Create a nic to be used as a parent for macvlan/ipvlan network.
nicName1 := createNetworkName("nic")[:8]
nicName2 := createNetworkName("nic")[:8]

netName1 := createNetworkName(driverType)
netName2 := createNetworkName(driverType)

parent1 := "parent=" + nicName1
parent2 := "parent=" + nicName2

defer deleteNetworkDevice(nicName1)
createNetworkDevice(nicName1)

defer podmanTest.removeNetwork(netName1)
nc1 := podmanTest.Podman([]string{"network", "create", "-d", driverType, "-o", parent1, "--subnet", "10.10.0.0/24", netName1})
nc1.WaitWithDefaultTimeout()
Expect(nc1).Should(ExitCleanly())

ctr := podmanTest.Podman([]string{"run", "-d", "--network", netName1, "--name", "test", ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr).Should(ExitCleanly())

exec1 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", "eth0"})
exec1.WaitWithDefaultTimeout()
Expect(exec1).Should(ExitCleanly())
Expect(exec1.OutputToString()).Should(ContainSubstring("eth0"))

defer deleteNetworkDevice(nicName2)
createNetworkDevice(nicName2)

defer podmanTest.removeNetwork(netName2)
nc2 := podmanTest.Podman([]string{"network", "create", "-d", driverType, "-o", parent2, "--subnet", "10.25.40.0/24", netName2})
nc2.WaitWithDefaultTimeout()
Expect(nc2).Should(ExitCleanly())

conn := podmanTest.Podman([]string{"network", "connect", netName2, "test"})
conn.WaitWithDefaultTimeout()
Expect(conn).Should(ExitCleanly())

vikas-goel marked this conversation as resolved.
Show resolved Hide resolved
exec2 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", "eth1"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(ExitCleanly())
Expect(exec2.OutputToString()).Should(ContainSubstring("eth1"))

rm := podmanTest.Podman([]string{"rm", "--time=0", "-f", "test"})
rm.WaitWithDefaultTimeout()
Expect(rm).Should(ExitCleanly())
}
})

It("podman container interface name with device scheme for macvlan/ipvlan network with parent", func() {
// Assert that the network interface name inside container for
// macvlan/ipvlan network, created with a specific parent
// interface, is the parent interface name ethX when
// interface_name in the containers.conf file is set to "device"

SkipIfRootless("cannot create network device in rootless mode.")

createContainersConfFileWithDeviceIfaceName(podmanTest)

for _, driverType := range []string{"macvlan", "ipvlan"} {
if driverType == "ipvlan" && isDebianRunc(podmanTest) {
GinkgoWriter.Println("FIXME: Fails with netavark < 1.10. Re-enable once Debian gets an update")
continue
}

// Create a nic to be used as a parent for the network.
nicName1 := createNetworkName("nic")[:8]
nicName2 := createNetworkName("nic")[:8]

netName1 := createNetworkName(driverType)
netName2 := createNetworkName(driverType)

parent1 := "parent=" + nicName1
parent2 := "parent=" + nicName2

defer deleteNetworkDevice(nicName1)
createNetworkDevice(nicName1)

defer podmanTest.removeNetwork(netName1)
nc1 := podmanTest.Podman([]string{"network", "create", "-d", driverType, "-o", parent1, "--subnet", "10.10.0.0/24", netName1})
nc1.WaitWithDefaultTimeout()
Expect(nc1).Should(ExitCleanly())

ctr := podmanTest.Podman([]string{"run", "-d", "--network", netName1, "--name", "test", ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr).Should(ExitCleanly())

exec1 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", nicName1})
exec1.WaitWithDefaultTimeout()
Expect(exec1).Should(ExitCleanly())
Expect(exec1.OutputToString()).Should(ContainSubstring(nicName1))

defer deleteNetworkDevice(nicName2)
createNetworkDevice(nicName2)

defer podmanTest.removeNetwork(netName2)
nc2 := podmanTest.Podman([]string{"network", "create", "-d", driverType, "-o", parent2, "--subnet", "10.25.40.0/24", netName2})
nc2.WaitWithDefaultTimeout()
Expect(nc2).Should(ExitCleanly())

conn := podmanTest.Podman([]string{"network", "connect", netName2, "test"})
conn.WaitWithDefaultTimeout()
Expect(conn).Should(ExitCleanly())

exec2 := podmanTest.Podman([]string{"exec", "test", "ip", "addr", "show", nicName2})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(ExitCleanly())
Expect(exec2.OutputToString()).Should(ContainSubstring(nicName2))

rm := podmanTest.Podman([]string{"rm", "--time=0", "-f", "test"})
rm.WaitWithDefaultTimeout()
Expect(rm).Should(ExitCleanly())
}
})
})
Loading