Skip to content

Commit

Permalink
Set interface name to the network_interface name for macvlan and ipvl…
Browse files Browse the repository at this point in the history
…an networks

When interface_name attribute in containers.conf file is set to "device", then set interface names inside containers same as the network_interface names of the respective network.

The change applies to macvlan and ipvlan networks only. The interface_name attribute value has no impact on any other types of networks.

If the interface name is set in the user request, then that takes precedence.

Fixes: #21313

Signed-off-by: Vikas Goel <[email protected]>
  • Loading branch information
vikas-goel committed Feb 7, 2024
1 parent 855a7cf commit a8b2256
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 8 deletions.
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()
Expect(session).Should(ExitCleanly())
}

func deleteNetworkDevice(name string) {
session := SystemExec("ip", []string{"link", "delete", name})
session.WaitWithDefaultTimeout()
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())
}
})

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())

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())
}
})
})

0 comments on commit a8b2256

Please sign in to comment.