agent: reconcile VFIO netdev MAC before UpdateInterface lookup

When a VFIO cold-plugged network device appears in guest with a
different MAC than the runtime request, resolve the netdev by PCI path
and apply the requested MAC before the normal by-MAC update flow.

This preserves existing behavior while avoiding UpdateInterface
mismatches in SR-IOV cold-plug cases.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
Assisted-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Fabiano Fidêncio
2026-05-27 19:29:22 +02:00
parent e89eb77245
commit 118b7fa611
2 changed files with 108 additions and 3 deletions

View File

@@ -3,6 +3,10 @@
// SPDX-License-Identifier: Apache-2.0
//
#[cfg(not(target_arch = "s390x"))]
use crate::linux_abi::{create_pci_root_bus_path, pcipath_from_dev_tree_path, SYSFS_DIR};
#[cfg(not(target_arch = "s390x"))]
use crate::{device::pcipath_to_sysfs, pci};
use anyhow::{anyhow, Context, Result};
use futures::{future, TryStreamExt};
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
@@ -227,6 +231,60 @@ impl Handle {
Ok(())
}
#[cfg(not(target_arch = "s390x"))]
pub fn netdev_name_from_pci_path(&self, dev_tree_path: &str) -> Result<Option<String>> {
let (root_complex, pcipath) = pcipath_from_dev_tree_path(dev_tree_path)
.with_context(|| format!("invalid PCI path for network interface: {dev_tree_path}"))?;
let root_bus_sysfs = format!("{}{}", SYSFS_DIR, create_pci_root_bus_path(root_complex));
let sysfs_rel_path = pcilib_to_sysfs_path(&root_bus_sysfs, &pcipath)?;
let net_dir = format!("{root_bus_sysfs}{sysfs_rel_path}/net");
let mut entries = match fs::read_dir(&net_dir) {
Ok(entries) => entries,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e).with_context(|| format!("failed to read net dir {net_dir}")),
};
if let Some(entry) = entries.next() {
let entry = entry.with_context(|| format!("failed to read entry under {net_dir}"))?;
let name = entry.file_name().into_string().map_err(|non_utf8| {
anyhow!("non-UTF8 netdev name under {}: {:?}", net_dir, non_utf8)
})?;
return Ok(Some(name));
}
Ok(None)
}
#[cfg(not(target_arch = "s390x"))]
pub async fn set_link_mac_by_name(&self, ifname: &str, mac: &str) -> Result<String> {
let link = self.find_link(LinkFilter::Name(ifname)).await?;
let prev_mac = link.address();
if prev_mac.eq_ignore_ascii_case(mac) {
return Ok(prev_mac);
}
let parsed_mac = parse_mac_address(mac)
.with_context(|| format!("failed to parse MAC address: {mac}"))?;
if link.is_up() {
self.enable_link(link.index(), false).await?;
}
let mut request = self.handle.link().set(link.index());
request.message_mut().header = link.header.clone();
request
.address(parsed_mac.to_vec())
.execute()
.await
.with_context(|| format!("failed to set MAC for interface {} to {}", ifname, mac))?;
if link.is_up() {
self.enable_link(link.index(), true).await?;
}
Ok(prev_mac)
}
/// Retireve available network interfaces.
pub async fn list_interfaces(&self) -> Result<Vec<Interface>> {
let mut list = Vec::new();
@@ -668,6 +726,11 @@ impl Handle {
}
}
#[cfg(not(target_arch = "s390x"))]
fn pcilib_to_sysfs_path(root_bus_sysfs: &str, pcipath: &pci::Path) -> Result<String> {
pcipath_to_sysfs(root_bus_sysfs, pcipath)
}
fn format_address(data: &[u8]) -> Result<String> {
match data.len() {
4 => {

View File

@@ -1150,9 +1150,51 @@ impl agent_ttrpc::AgentService for AgentService {
}
}
self.sandbox
.lock()
.await
let mut sandbox = self.sandbox.lock().await;
#[cfg(not(target_arch = "s390x"))]
if !interface.devicePath.is_empty() && !interface.hwAddr.is_empty() {
match sandbox
.rtnl
.netdev_name_from_pci_path(&interface.devicePath)
{
Ok(Some(netdev_name)) => {
if let Err(err) = sandbox
.rtnl
.set_link_mac_by_name(&netdev_name, &interface.hwAddr)
.await
{
warn!(
sl(),
"update_interface: VFIO MAC reconciliation failed, fallback to by-MAC lookup";
"device-path" => interface.devicePath.as_str(),
"target-mac" => interface.hwAddr.as_str(),
"netdev" => netdev_name.as_str(),
"error" => format!("{:?}", err),
);
}
}
Ok(None) => {
info!(
sl(),
"update_interface: no netdev found for PCI path before by-MAC lookup";
"device-path" => interface.devicePath.as_str(),
"target-mac" => interface.hwAddr.as_str(),
);
}
Err(err) => {
warn!(
sl(),
"update_interface: unable to resolve netdev from PCI path, fallback to by-MAC lookup";
"device-path" => interface.devicePath.as_str(),
"target-mac" => interface.hwAddr.as_str(),
"error" => format!("{:?}", err),
);
}
}
}
sandbox
.rtnl
.update_interface(&interface)
.await