Add support for Linux Network Devices

Implement support for passing Linux Network Devices to the container
network namespace.

The network device is passed during the creation of the container,
before the process is started.

It implements the logic defined in the OCI runtime specification.

Signed-off-by: Antonio Ojea <aojea@google.com>
This commit is contained in:
Antonio Ojea
2025-02-06 08:43:32 +00:00
committed by Antonio Ojea
parent 889c7b272f
commit 8d180e9658
13 changed files with 794 additions and 1 deletions
+95
View File
@@ -2,14 +2,34 @@
load helpers
function create_netns() {
# Create a temporary name for the test network namespace.
tmp=$(mktemp -u)
ns_name=$(basename "$tmp")
# Create the network namespace.
ip netns add "$ns_name"
ns_path=$(ip netns add "$ns_name" 2>&1 | sed -e 's/.*"\(.*\)".*/\1/')
}
function delete_netns() {
# Delete the namespace only if the ns_name variable is set.
[ -v ns_name ] && ip netns del "$ns_name"
}
function setup() {
# XXX: currently criu require root containers.
requires criu root
setup_busybox
# Create a dummy interface to move to the container.
ip link add dummy0 type dummy
}
function teardown() {
ip link del dev dummy0
delete_netns
teardown_bundle
}
@@ -122,6 +142,52 @@ function simple_cr() {
done
}
function simple_cr_with_netdevice() {
# Set custom parameters to the netdevice to validate those are respected
mtu_value=1789
mac_address="00:11:22:33:44:55"
global_ip="169.254.169.77/32"
ip link set mtu "$mtu_value" dev dummy0
ip link set address "$mac_address" dev dummy0
ip address add "$global_ip" dev dummy0
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
update_config ' .linux.netDevices |= {"dummy0": {} }'
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox_netdevice
[ "$status" -eq 0 ]
testcontainer test_busybox_netdevice running
run runc exec test_busybox_netdevice ip address show dev dummy0
[ "$status" -eq 0 ]
[[ "$output" == *" $global_ip "* ]]
[[ "$output" == *"ether $mac_address "* ]]
[[ "$output" == *"mtu $mtu_value "* ]]
for _ in $(seq 2); do
# checkpoint the running container
runc "$@" checkpoint --work-path ./work-dir test_busybox_netdevice
[ "$status" -eq 0 ]
# after checkpoint busybox is no longer running
testcontainer test_busybox_netdevice checkpointed
# restore from checkpoint
runc "$@" restore -d --work-path ./work-dir --console-socket "$CONSOLE_SOCKET" test_busybox_netdevice
[ "$status" -eq 0 ]
# busybox should be back up and running
testcontainer test_busybox_netdevice running
run runc exec test_busybox_netdevice ip address show dev dummy0
[ "$status" -eq 0 ]
[[ "$output" == *" $global_ip "* ]]
[[ "$output" == *"ether $mac_address "* ]]
[[ "$output" == *"mtu $mtu_value "* ]]
done
}
@test "checkpoint and restore" {
simple_cr
}
@@ -151,6 +217,35 @@ function simple_cr() {
simple_cr
}
@test "checkpoint and restore with netdevice" {
simple_cr_with_netdevice
}
@test "checkpoint and restore with netdevice (bind mount, destination is symlink)" {
mkdir -p rootfs/real/conf
ln -s /real/conf rootfs/conf
update_config ' .mounts += [{
source: ".",
destination: "/conf",
options: ["bind"]
}]'
simple_cr_with_netdevice
}
@test "checkpoint and restore with netdevice (with --debug)" {
simple_cr_with_netdevice --debug
}
@test "checkpoint and restore with netdevice (cgroupns)" {
# cgroupv2 already enables cgroupns so this case was tested above already
requires cgroups_v1 cgroupns
# enable CGROUPNS
update_config '.linux.namespaces += [{"type": "cgroup"}]'
simple_cr_with_netdevice
}
@test "checkpoint --pre-dump (bad --parent-path)" {
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env bats
load helpers
function create_netns() {
# Create a temporary name for the test network namespace.
tmp=$(mktemp -u)
ns_name=$(basename "$tmp")
# Create the network namespace.
ip netns add "$ns_name"
ns_path=$(ip netns add "$ns_name" 2>&1 | sed -e 's/.*"\(.*\)".*/\1/')
}
function delete_netns() {
# Delete the namespace only if the ns_name variable is set.
[ -v ns_name ] && ip netns del "$ns_name"
}
function setup() {
requires root
setup_busybox
# Create a dummy interface to move to the container.
ip link add dummy0 type dummy
}
function teardown() {
ip link del dev dummy0
delete_netns
teardown_bundle
}
@test "move network device to container network namespace" {
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
runc run test_busybox
[ "$status" -eq 0 ]
}
@test "move network device to container network namespace and restore it back" {
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
update_config ' .linux.netDevices |= {"dummy0": {} }'
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]
# The network namespace owner controls the lifecycle of the interface.
# The interface should remain on the namespace after the container was killed.
runc delete --force test_busybox
# Move back the interface to the root namespace (pid 1).
ip netns exec "$ns_name" ip link set dev dummy0 netns 1
# Verify the interface is back in the root network namespace.
ip address show dev dummy0
}
@test "move network device to precreated container network namespace" {
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
runc run test_busybox
[ "$status" -eq 0 ]
# Verify the interface is still present in the network namespace.
ip netns exec "$ns_name" ip address show dev dummy0
}
@test "move network device to precreated container network namespace and set ip address with global scope" {
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
global_ip="169.254.169.77/32"
# Set the interface down to avoid possible network problems.
# Set a custom address to the interface.
ip link set down dev dummy0
ip address add "$global_ip" dev dummy0
runc run test_busybox
[ "$status" -eq 0 ]
[[ "$output" == *"$global_ip "* ]]
# Verify the interface is still present in the network namespace.
run ip netns exec "$ns_name" ip address show dev dummy0
[ "$status" -eq 0 ]
[[ "$output" == *"$global_ip "* ]]
}
@test "move network device to precreated container network namespace and set ip address without global scope" {
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
non_global_ip="127.0.0.33"
# Set the interface down to avoid possible network problems.
# Set a custom address to the interface.
ip link set down dev dummy0
ip address add "$non_global_ip" dev dummy0
runc run test_busybox
[ "$status" -eq 0 ]
[[ "$output" != *" $non_global_ip "* ]]
# Verify the interface is still present in the network namespace.
ip netns exec "$ns_name" ip address show dev dummy0
}
@test "move network device to precreated container network namespace and set mtu" {
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
mtu_value=1789
# Cet a custom mtu to the interface.
ip link set mtu "$mtu_value" dev dummy0
runc run test_busybox
[ "$status" -eq 0 ]
[[ "$output" == *"mtu $mtu_value "* ]]
# Verify the interface is still present in the network namespace.
run ip netns exec "$ns_name" ip address show dev dummy0
[ "$status" -eq 0 ]
[[ "$output" == *"mtu $mtu_value "* ]]
}
@test "move network device to precreated container network namespace and set mac address" {
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
mac_address="00:11:22:33:44:55"
# set a custom mac address to the interface
ip link set address "$mac_address" dev dummy0
runc run test_busybox
[ "$status" -eq 0 ]
[[ "$output" == *"ether $mac_address "* ]]
# Verify the interface is still present in the network namespace.
run ip netns exec "$ns_name" ip address show dev dummy0
[ "$status" -eq 0 ]
[[ "$output" == *"ether $mac_address "* ]]
}
@test "move network device to precreated container network namespace and rename" {
update_config ' .linux.netDevices |= { "dummy0": { "name" : "ctr_dummy0" } }
| .process.args |= ["ip", "address", "show", "dev", "ctr_dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
runc run test_busybox
[ "$status" -eq 0 ]
# Verify the interface is still present in the network namespace.
ip netns exec "$ns_name" ip address show dev ctr_dummy0
}
@test "move network device to precreated container network namespace and rename and set mtu and mac and ip address" {
update_config ' .linux.netDevices |= { "dummy0": { "name" : "ctr_dummy0" } }
| .process.args |= ["ip", "address", "show", "dev", "ctr_dummy0"]'
# Tell runc which network namespace to use.
create_netns
update_config '(.. | select(.type? == "network")) .path |= "'"$ns_path"'"'
mtu_value=1789
mac_address="00:11:22:33:44:55"
global_ip="169.254.169.77/32"
# Set a custom mtu to the interface.
ip link set mtu "$mtu_value" dev dummy0
# Set a custom mac address to the interface.
ip link set address "$mac_address" dev dummy0
# Set a custom ip address to the interface.
ip address add "$global_ip" dev dummy0
runc run test_busybox
[ "$status" -eq 0 ]
[[ "$output" == *" $global_ip "* ]]
[[ "$output" == *"ether $mac_address "* ]]
[[ "$output" == *"mtu $mtu_value "* ]]
# Verify the interface is still present in the network namespace.
run ip netns exec "$ns_name" ip address show dev ctr_dummy0
[ "$status" -eq 0 ]
[[ "$output" == *" $global_ip "* ]]
[[ "$output" == *"ether $mac_address "* ]]
[[ "$output" == *"mtu $mtu_value "* ]]
}
+36
View File
@@ -246,3 +246,39 @@ function teardown() {
[ "$status" -eq 0 ]
[[ "$output" == "$netns_id" ]]
}
@test "userns with network interface" {
requires root
# Create a dummy interface to move to the container.
ip link add dummy0 type dummy
update_config ' .linux.netDevices |= {"dummy0": {} }
| .process.args |= ["ip", "address", "show", "dev", "dummy0"]'
runc run test_busybox
[ "$status" -eq 0 ]
# The interface is virtual and should not exist because
# is deleted during the namespace cleanup.
run ip link del dummy0
[ "$status" -ne 0 ]
}
@test "userns with network interface renamed" {
requires root
# Create a dummy interface to move to the container.
ip link add dummy0 type dummy
update_config ' .linux.netDevices |= { "dummy0": { "name" : "ctr_dummy0" } }
| .process.args |= ["ip", "address", "show", "dev", "ctr_dummy0"]'
runc run test_busybox
[ "$status" -eq 0 ]
# The interface is virtual and should not exist because
# is deleted during the namespace cleanup.
run ip link del dummy0
[ "$status" -ne 0 ]
}