Skip to main content

libcontainer/network/
network_device.rs

1use std::os::fd::RawFd;
2
3use netlink_packet_route::address::{AddressHeaderFlags, AddressScope};
4use oci_spec::runtime::LinuxNetDevice;
5
6use super::Result;
7use super::address::AddressClient;
8use super::link::LinkClient;
9use super::wrapper::create_network_client;
10use crate::network::cidr::CidrAddress;
11
12/// Resolves the final name for a network device.
13/// If the device has a configured name (non-empty), use it; otherwise use the original name.
14pub fn resolve_device_name<'a>(device: &'a LinuxNetDevice, original_name: &'a str) -> &'a str {
15    device
16        .name()
17        .as_ref()
18        .filter(|d| !d.is_empty())
19        .map_or(original_name, |d| d)
20}
21
22/// dev_change_netns allows to move a device given by name to a network namespace given by netns_fd
23/// and optionally change the device name.
24/// The device name will be kept the same if device.Name is None or an empty string.
25/// This function ensures that the move and rename operations occur atomically.
26/// It preserves existing interface attributes, including IP addresses.
27pub fn dev_change_net_namespace(
28    name: &str,
29    netns_fd: RawFd,
30    device: &LinuxNetDevice,
31) -> Result<Vec<CidrAddress>> {
32    tracing::debug!(
33        "attaching network device {} to network namespace fd {}",
34        name,
35        netns_fd
36    );
37
38    let mut link_client = LinkClient::new(create_network_client())?;
39    let mut addr_client = AddressClient::new(create_network_client())?;
40
41    let new_name = resolve_device_name(device, name);
42
43    let link = link_client.get_by_name(name)?;
44
45    let index = link.header.index;
46
47    // Set the interface link state to DOWN before modifying attributes like namespace or name.
48    // This prevents potential conflicts or disruptions on the host network during the transition,
49    // particularly if other host components depend on this specific interface or its properties.
50    link_client.set_down(index)?;
51
52    // Get the existing IP addresses on the interface.
53    let addrs = addr_client.get_by_index(index)?;
54
55    link_client
56        .set_ns_fd(index, new_name, netns_fd)
57        .map_err(|err| {
58            tracing::error!(?err, "failed to set_ns_fd");
59            err
60        })?;
61
62    // Filter addresses before sending to init process:
63    // Only include IP addresses with global scope and permanent flag.
64    let cidr_addrs: Vec<CidrAddress> = addrs
65        .iter()
66        .filter(|addr| {
67            // Only move IP addresses with global scope because those are not host-specific, auto-configured,
68            // or have limited network scope, making them unsuitable inside the container namespace.
69            // Ref: https://www.ietf.org/rfc/rfc3549.txt
70            if addr.header.scope != AddressScope::Universe {
71                tracing::debug!(
72                    "skipping address with scope {:?} from network device {}",
73                    addr.header.scope,
74                    new_name
75                );
76                return false;
77            }
78
79            // Only move permanent IP addresses configured by the user, dynamic addresses are excluded because
80            // their validity may rely on the original network namespace's context and they may have limited
81            // lifetimes and are not guaranteed to be available in a new namespace.
82            // Ref: https://www.ietf.org/rfc/rfc3549.txt
83            if !addr.header.flags.contains(AddressHeaderFlags::Permanent) {
84                tracing::debug!(
85                    "skipping non-permanent address from network device {}",
86                    new_name
87                );
88                return false;
89            }
90
91            true
92        })
93        .map(CidrAddress::from)
94        .collect();
95
96    Ok(cidr_addrs)
97}
98
99/// Core logic for setting up addresses in the new network namespace
100/// This function is extracted to make it testable without system calls
101///
102/// Note: The addresses passed to this function are already filtered in the main process
103/// to include only global scope and permanent addresses.
104pub fn setup_addresses_in_network_namespace(
105    addrs: &[CidrAddress],
106    link_index: u32,
107    new_name: &str,
108    addr_client: &mut AddressClient,
109) -> Result<()> {
110    // Re-add the original IP addresses to the interface in the new namespace.
111    // The kernel removes IP addresses when an interface is moved between network namespaces.
112    for addr in addrs {
113        tracing::debug!(
114            "adding address {:?}/{} to network device {}",
115            addr.address,
116            addr.prefix_len,
117            new_name
118        );
119        addr_client.add(link_index, addr.address, addr.prefix_len)?;
120    }
121
122    Ok(())
123}
124
125#[cfg(test)]
126mod tests {
127    use std::net::{IpAddr, Ipv4Addr};
128
129    use netlink_packet_route::RouteNetlinkMessage;
130    use netlink_packet_route::address::{AddressAttribute, AddressMessage};
131
132    use super::*;
133    use crate::network::address::AddressClient;
134    use crate::network::fake::FakeNetlinkClient;
135    use crate::network::wrapper::ClientWrapper;
136
137    #[test]
138    fn test_setup_addresses_in_network_namespace() {
139        let mut fake_client = FakeNetlinkClient::new();
140
141        let mut addr_msg = AddressMessage::default();
142        addr_msg.header.scope = AddressScope::Universe;
143        addr_msg.header.prefix_len = 24;
144        addr_msg.header.flags = AddressHeaderFlags::Permanent;
145        addr_msg
146            .attributes
147            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(
148                192, 168, 1, 1,
149            ))));
150
151        let responses = vec![RouteNetlinkMessage::NewAddress(addr_msg.clone())];
152        fake_client.set_expected_responses(responses);
153
154        let mut addr_client = AddressClient::new(ClientWrapper::Fake(fake_client)).unwrap();
155
156        let addrs = [addr_msg];
157        let serializable_addrs: Vec<CidrAddress> = addrs.iter().map(CidrAddress::from).collect();
158        let result =
159            setup_addresses_in_network_namespace(&serializable_addrs, 5, "eth1", &mut addr_client);
160        assert!(result.is_ok());
161
162        // Verify the call was tracked
163        if let Some(send_calls) = addr_client.get_send_calls() {
164            assert_eq!(send_calls.len(), 1);
165        } else {
166            panic!("Expected Fake client");
167        }
168    }
169
170    #[test]
171    fn test_resolve_device_name_with_name() {
172        let device = LinuxNetDevice::default()
173            .set_name(Some("eth0".to_string()))
174            .clone();
175        let original = "veth0";
176
177        let result = resolve_device_name(&device, original);
178        assert_eq!(result, "eth0");
179    }
180
181    #[test]
182    fn test_resolve_device_name_with_empty_name() {
183        let device = LinuxNetDevice::default()
184            .set_name(Some("".to_string()))
185            .clone();
186        let original = "veth0";
187
188        let result = resolve_device_name(&device, original);
189        assert_eq!(result, "veth0");
190    }
191
192    #[test]
193    fn test_resolve_device_name_without_name() {
194        let device = LinuxNetDevice::default();
195        let original = "veth0";
196
197        let result = resolve_device_name(&device, original);
198        assert_eq!(result, "veth0");
199    }
200}