Files
cloud-hypervisor/net_util/src/open_tap.rs
Sebastien Boeuf e37f63282c net_util: Only set host MAC address from user input
In case the host MAC address associated with a TAP device wasn't
explicitly provided by the user, Cloud Hypervisor would get the host MAC
associated by default with this TAP device and store it through the
network config. Problem is, in the context of a snapshot/restore, that
meant the network config provided by the user was different on the
destination host compared to the source host. This was causing an issue
when Cloud Hypervisor wasn't started with CAP_NET_ADMIN permissions as
it couldn't set the host MAC address on the destination, while the
source never needed these permissions since the MAC was automatically
allocated by the kernel.

We're fixing this issue by setting the host MAC address when it's
explicitly requested by the user through the network config, and making
the host MAC immutable so that it can't be changed at runtime.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
2026-07-18 09:42:40 +00:00

144 lines
5.1 KiB
Rust

// Copyright (c) 2020 Intel Corporation. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::net::IpAddr;
use std::path::Path;
use std::{fs, io, num, result};
use log::warn;
use thiserror::Error;
use super::{MacAddr, Tap, TapError, vnet_hdr_len};
#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to convert an hexadecimal string into an integer")]
ConvertHexStringToInt(#[source] num::ParseIntError),
#[error("Error related to the multiqueue support (no support TAP side)")]
MultiQueueNoTapSupport,
#[error("Error related to the multiqueue support (no support device side)")]
MultiQueueNoDeviceSupport,
#[error("Failed to read the TAP flags from sysfs")]
ReadSysfsTunFlags(#[source] io::Error),
#[error("Open tap device failed")]
TapOpen(#[source] TapError),
#[error("Setting tap IP and/or netmask failed")]
TapSetIpNetmask(#[source] TapError),
#[error("Setting MAC address failed")]
TapSetMac(#[source] TapError),
#[error("Setting vnet header size failed")]
TapSetVnetHdrSize(#[source] TapError),
#[error("Setting MTU failed")]
TapSetMtu(#[source] TapError),
#[error("Enabling tap interface failed")]
TapEnable(#[source] TapError),
}
type Result<T> = result::Result<T, Error>;
fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> {
if let Some(tap_name) = if_name {
let mq = queue_pairs > 1;
let path = format!("/sys/class/net/{tap_name}/tun_flags");
// interface does not exist, check is not required
if !Path::new(&path).exists() {
return Ok(());
}
let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?;
let tun_flags = i32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16)
.map_err(Error::ConvertHexStringToInt)?;
if (tun_flags & libc::IFF_MULTI_QUEUE != 0) && !mq {
return Err(Error::MultiQueueNoDeviceSupport);
} else if (tun_flags & libc::IFF_MULTI_QUEUE == 0) && mq {
return Err(Error::MultiQueueNoTapSupport);
}
}
Ok(())
}
/// Opens a Tap device and configures it.
///
/// Afterward, further RX queues can be opened with a common config.
fn open_tap_rx_q_0(
if_name: Option<&str>,
ip_addr: Option<IpAddr>,
netmask: Option<IpAddr>,
host_mac: Option<MacAddr>,
mtu: Option<u16>,
num_rx_q: usize,
flags: Option<i32>,
) -> Result<Tap> {
// Check if the given interface exists before we create it.
let tap_exists = if_name.is_some_and(|n| Path::new(&format!("/sys/class/net/{n}")).exists());
let tap = match if_name {
Some(name) => Tap::open_named(name, num_rx_q, flags).map_err(Error::TapOpen)?,
// Create a new Tap device in Linux, if none was specified.
None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
};
// Don't overwrite ip configuration of existing interfaces:
if tap_exists {
warn!(
"Tap {} already exists. IP configuration will not be overwritten.",
if_name.unwrap_or_default()
);
} else if let Some(ip) = ip_addr {
tap.set_ip_addr(ip, netmask)
.map_err(Error::TapSetIpNetmask)?;
}
if let Some(mac) = host_mac {
tap.set_mac_addr(mac).map_err(Error::TapSetMac)?;
}
if let Some(mtu) = mtu {
tap.set_mtu(mtu as i32).map_err(Error::TapSetMtu)?;
}
tap.enable().map_err(Error::TapEnable)?;
tap.set_vnet_hdr_size(vnet_hdr_len() as i32)
.map_err(Error::TapSetVnetHdrSize)?;
Ok(tap)
}
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn open_tap(
if_name: Option<&str>,
ip_addr: Option<IpAddr>,
netmask: Option<IpAddr>,
host_mac: Option<MacAddr>,
mtu: Option<u16>,
num_rx_q: usize,
flags: Option<i32>,
) -> Result<Vec<Tap>> {
let mut taps: Vec<Tap> = Vec::new();
let mut ifname: String = String::new();
// In case the tap interface already exists, check if the number of
// queues is appropriate. The tap might not support multiqueue while
// the number of queues indicates the user expects multiple queues, or
// on the contrary, the tap might support multiqueue while the number
// of queues indicates the user doesn't expect multiple queues.
check_mq_support(&if_name, num_rx_q)?;
for i in 0..num_rx_q {
let tap: Tap;
if i == 0 {
// Special handling is required for the first RX queue, such as
// configuring the device. Subsequent iterations will then use the
// same device.
tap = open_tap_rx_q_0(if_name, ip_addr, netmask, host_mac, mtu, num_rx_q, flags)?;
// Set the name of the tap device we open in subsequent iterations.
ifname = tap.if_name_as_str().to_string();
} else {
tap = Tap::open_named(ifname.as_str(), num_rx_q, flags).map_err(Error::TapOpen)?;
tap.set_vnet_hdr_size(vnet_hdr_len() as i32)
.map_err(Error::TapSetVnetHdrSize)?;
}
taps.push(tap);
}
Ok(taps)
}