vmm: allow net devices without ip and mask

This change enables easier integration with third-party
tools by removing the requirement for a dummy IP address
when configuring tap devices. The modification applies to
both CLI and API interactions.

Previously, cloud-hypervisor would automatically set a
default static IP address (192.168.249.1) if none was provided.

This could lead to:

* multiple devices without explicit IP configurations
  would end up with the same default IP
* unnecessary inclusion of this IP in firewall rules
* the IP address could clash with host networking and
  routing

This introduces a new constraint:
When providing an IP, the mask must also be provided.

Removes warnings introduced in #7179.
Closes issue #7083.

Signed-off-by: Maximilian Güntner <code@mguentner.de>
This commit is contained in:
Maximilian Güntner 2025-09-22 10:43:03 +02:00 committed by Rob Bradford
parent d28d9eb34e
commit 66aa0743f0
5 changed files with 58 additions and 51 deletions

View file

@ -36,7 +36,7 @@ struct OptionParserValue {
requires_value: bool,
}
#[derive(Error, Debug)]
#[derive(Debug, Error)]
pub enum OptionParserError {
#[error("unknown option: {0}")]
UnknownOption(String),

View file

@ -1336,20 +1336,6 @@ mod unit_tests {
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",

View file

@ -357,6 +357,10 @@ pub enum ValidationError {
InvalidIvshmemPath,
#[error("Payload configuration is not bootable")]
PayloadError(#[from] PayloadConfigError),
#[error("Mask provided without an IP")]
MaskProvidedWithoutIp,
#[error("IP provided without a mask")]
IpProvidedWithoutMask,
}
type ValidationResult<T> = std::result::Result<T, ValidationError>;
@ -1331,14 +1335,9 @@ impl NetConfig {
parser.parse(net).map_err(Error::ParseNetwork)?;
let tap = parser.get("tap");
let ip = parser
.convert("ip")
.map_err(Error::ParseNetwork)?
.unwrap_or_else(default_netconfig_ip);
let mask = parser
.convert("mask")
.map_err(Error::ParseNetwork)?
.unwrap_or_else(default_netconfig_mask);
let ip = parser.convert("ip").map_err(Error::ParseNetwork)?;
let mask = parser.convert("mask").map_err(Error::ParseNetwork)?;
let mac = parser
.convert("mac")
.map_err(Error::ParseNetwork)?
@ -1515,6 +1514,14 @@ impl NetConfig {
return Err(ValidationError::NoHardwareChecksumOffload);
}
if self.mask.is_some() && self.ip.is_none() {
return Err(ValidationError::MaskProvidedWithoutIp);
}
if self.ip.is_some() && self.mask.is_none() {
return Err(ValidationError::IpProvidedWithoutMask);
}
Ok(())
}
}
@ -3177,7 +3184,6 @@ impl Drop for VmConfig {
#[cfg(test)]
mod tests {
use std::fs::File;
use std::net::{IpAddr, Ipv4Addr};
use std::os::unix::io::AsRawFd;
use net_util::MacAddr;
@ -3507,8 +3513,8 @@ mod tests {
fn net_fixture() -> NetConfig {
NetConfig {
tap: None,
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)),
mask: IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)),
ip: None,
mask: None,
mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(),
host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()),
mtu: None,
@ -3550,8 +3556,8 @@ mod tests {
)?,
NetConfig {
tap: Some("tap0".to_owned()),
ip: "192.168.100.1".parse().unwrap(),
mask: "255.255.255.128".parse().unwrap(),
ip: Some("192.168.100.1".parse().unwrap()),
mask: Some("255.255.255.128".parse().unwrap()),
..net_fixture()
}
);
@ -3589,6 +3595,15 @@ mod tests {
}
);
assert_eq!(
NetConfig::parse("mac=de:ad:be:ef:12:34,mask=255.255.255.0")?,
NetConfig {
mask: Some("255.255.255.0".parse().unwrap()),
host_mac: None,
..net_fixture()
}
);
Ok(())
}
@ -4289,6 +4304,28 @@ mod tests {
Err(ValidationError::NoHardwareChecksumOffload)
);
let mut invalid_config = valid_config.clone();
invalid_config.net = Some(vec![NetConfig {
ip: None,
mask: Some("255.255.255.0".parse().unwrap()),
..net_fixture()
}]);
assert_eq!(
invalid_config.validate(),
Err(ValidationError::MaskProvidedWithoutIp)
);
let mut invalid_config = valid_config.clone();
invalid_config.net = Some(vec![NetConfig {
ip: Some("192.1.33.7".parse().unwrap()),
mask: None,
..net_fixture()
}]);
assert_eq!(
invalid_config.validate(),
Err(ValidationError::IpProvidedWithoutMask)
);
let mut invalid_config = valid_config.clone();
invalid_config.fs = Some(vec![fs_fixture()]);
assert_eq!(

View file

@ -2904,8 +2904,8 @@ impl DeviceManager {
virtio_devices::Net::new(
id.clone(),
Some(tap_if_name),
Some(net_cfg.ip),
Some(net_cfg.mask),
net_cfg.ip,
net_cfg.mask,
Some(net_cfg.mac),
&mut net_cfg.host_mac,
net_cfg.mtu,
@ -2955,8 +2955,8 @@ impl DeviceManager {
virtio_devices::Net::new(
id.clone(),
None,
Some(net_cfg.ip),
Some(net_cfg.mask),
net_cfg.ip,
net_cfg.mask,
Some(net_cfg.mac),
&mut net_cfg.host_mac,
net_cfg.mtu,

View file

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: Apache-2.0
//
use std::net::{IpAddr, Ipv4Addr};
use std::net::IpAddr;
use std::path::{Path, PathBuf};
#[cfg(feature = "fw_cfg")]
use std::str::FromStr;
@ -303,10 +303,8 @@ pub fn default_diskconfig_queue_size() -> u16 {
pub struct NetConfig {
#[serde(default = "default_netconfig_tap")]
pub tap: Option<String>,
#[serde(default = "default_netconfig_ip")]
pub ip: IpAddr,
#[serde(default = "default_netconfig_mask")]
pub mask: IpAddr,
pub ip: Option<IpAddr>,
pub mask: Option<IpAddr>,
#[serde(default = "default_netconfig_mac")]
pub mac: MacAddr,
#[serde(default)]
@ -352,20 +350,6 @@ pub fn default_netconfig_tap() -> Option<String> {
None
}
pub fn default_netconfig_ip() -> IpAddr {
warn!(
"Deprecation warning: No IP address provided. A default IP address is assigned. This behavior will be deprecated soon."
);
IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1))
}
pub fn default_netconfig_mask() -> IpAddr {
warn!(
"Deprecation warning: No network mask provided. A default network mask is assigned. This behavior will be deprecated soon."
);
IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0))
}
pub fn default_netconfig_mac() -> MacAddr {
MacAddr::local_random()
}