From 2b5a83bd9e2c1c9642773c1daf785d03f95f33a3 Mon Sep 17 00:00:00 2001
From: Alyssa Ross <hi@alyssa.is>
Date: Sun, 27 Sep 2020 15:34:02 +0000
Subject: [PATCH crosvm v3] crosvm: support setting guest MAC from tap-fd
This adds a mac= option to crosvm's --tap-fd option. The virtio-net
driver in the guest will read the desired MAC from virtio
configuration space.
See the documentation for VIRTIO_NET_F_MAC in the Virtio spec[1].
[1]: https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
Thanks-to: Puck Meerburg <puck@puckipedia.com>
Reviewed-by: Cole Helbling <cole.e.helbling@outlook.com>
Message-Id: <20210517185700.3591932-1-hi@alyssa.is>
---
devices/src/virtio/net.rs | 20 ++++++++++--
src/crosvm.rs | 8 +++--
src/linux.rs | 19 +++++++-----
src/main.rs | 64 +++++++++++++++++++++++++++++++--------
4 files changed, 88 insertions(+), 23 deletions(-)
diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
index b88dc44ae..b7489eb2b 100644
--- a/devices/src/virtio/net.rs
+++ b/devices/src/virtio/net.rs
@@ -419,6 +419,7 @@ where
}
pub struct Net<T: TapT> {
+ mac_address: Option<MacAddress>,
queue_sizes: Box<[u16]>,
workers_kill_evt: Vec<Event>,
kill_evts: Vec<Event>,
@@ -439,6 +440,7 @@ where
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
mac_addr: MacAddress,
+ guest_mac_addr: Option<MacAddress>,
vq_pairs: u16,
) -> Result<Net<T>, NetError> {
let multi_queue = vq_pairs > 1;
@@ -450,12 +452,17 @@ where
tap.enable().map_err(NetError::TapEnable)?;
- Net::from(base_features, tap, vq_pairs)
+ Net::with_tap(base_features, tap, vq_pairs, guest_mac_addr)
}
/// Creates a new virtio network device from a tap device that has already been
/// configured.
- pub fn from(base_features: u64, tap: T, vq_pairs: u16) -> Result<Net<T>, NetError> {
+ pub fn with_tap(
+ base_features: u64,
+ tap: T,
+ vq_pairs: u16,
+ mac_address: Option<MacAddress>,
+ ) -> Result<Net<T>, NetError> {
let taps = tap.into_mq_taps(vq_pairs).map_err(NetError::TapOpen)?;
// This would also validate a tap created by Self::new(), but that's a good thing as it
@@ -488,7 +495,12 @@ where
workers_kill_evt.push(worker_kill_evt);
}
+ if mac_address.is_some() {
+ avail_features |= 1 << virtio_net::VIRTIO_NET_F_MAC;
+ }
+
Ok(Net {
+ mac_address,
queue_sizes: vec![QUEUE_SIZE; (vq_pairs * 2 + 1) as usize].into_boxed_slice(),
workers_kill_evt,
kill_evts,
@@ -503,6 +515,10 @@ where
let vq_pairs = self.queue_sizes.len() as u16 / 2;
VirtioNetConfig {
+ mac: self
+ .mac_address
+ .map(|m| m.octets())
+ .unwrap_or_else(Default::default),
max_vq_pairs: Le16::from(vq_pairs),
// Other field has meaningful value when the corresponding feature
// is enabled, but all these features aren't supported now.
diff --git a/src/crosvm.rs b/src/crosvm.rs
index eededc02e..62b3019db 100644
--- a/src/crosvm.rs
+++ b/src/crosvm.rs
@@ -191,6 +191,10 @@ impl Default for SharedDir {
}
}
+pub struct TapFdOption {
+ pub mac: Option<net_util::MacAddress>,
+}
+
/// Aggregate of all configurable options for a running VM.
pub struct Config {
pub kvm_device_path: PathBuf,
@@ -217,7 +221,7 @@ pub struct Config {
pub mac_address: Option<net_util::MacAddress>,
pub net_vq_pairs: Option<u16>,
pub vhost_net: bool,
- pub tap_fd: Vec<RawFd>,
+ pub tap_fd: BTreeMap<RawFd, TapFdOption>,
pub cid: Option<u64>,
pub wayland_socket_paths: BTreeMap<String, PathBuf>,
pub wayland_dmabuf: bool,
@@ -291,7 +295,7 @@ impl Default for Config {
mac_address: None,
net_vq_pairs: None,
vhost_net: false,
- tap_fd: Vec::new(),
+ tap_fd: BTreeMap::new(),
cid: None,
#[cfg(feature = "gpu")]
gpu_parameters: None,
diff --git a/src/linux.rs b/src/linux.rs
index ba2d28f96..e9601478a 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -60,8 +60,8 @@ use vm_memory::{GuestAddress, GuestMemory};
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
use crate::gdb::{gdb_thread, GdbStub};
use crate::{
- Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption, VhostUserFsOption,
- VhostUserOption,
+ Config, DiskOption, Executable, SharedDir, SharedDirKind, TapFdOption, TouchDeviceOption,
+ VhostUserFsOption, VhostUserOption,
};
use arch::{
self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VcpuAffinity,
@@ -763,7 +763,11 @@ fn create_balloon_device(cfg: &Config, tube: Tube) -> DeviceResult {
})
}
-fn create_tap_net_device(cfg: &Config, tap_fd: RawDescriptor) -> DeviceResult {
+fn create_tap_net_device(
+ cfg: &Config,
+ tap_fd: RawDescriptor,
+ options: &TapFdOption,
+) -> DeviceResult {
// Safe because we ensure that we get a unique handle to the fd.
let tap = unsafe {
Tap::from_raw_descriptor(
@@ -779,7 +783,8 @@ fn create_tap_net_device(cfg: &Config, tap_fd: RawDescriptor) -> DeviceResult {
vq_pairs = 1;
}
let features = virtio::base_features(cfg.protected_vm);
- let dev = virtio::Net::from(features, tap, vq_pairs).map_err(Error::NetDeviceNew)?;
+ let dev =
+ virtio::Net::with_tap(features, tap, vq_pairs, options.mac).map_err(Error::NetDeviceNew)?;
Ok(VirtioDeviceStub {
dev: Box::new(dev),
@@ -814,7 +819,7 @@ fn create_net_device(
.map_err(Error::VhostNetDeviceNew)?;
Box::new(dev) as Box<dyn VirtioDevice>
} else {
- let dev = virtio::Net::<Tap>::new(features, host_ip, netmask, mac_address, vq_pairs)
+ let dev = virtio::Net::<Tap>::new(features, host_ip, netmask, mac_address, None, vq_pairs)
.map_err(Error::NetDeviceNew)?;
Box::new(dev) as Box<dyn VirtioDevice>
};
@@ -1445,8 +1450,8 @@ fn create_virtio_devices(
devs.push(create_balloon_device(cfg, balloon_device_tube)?);
// We checked above that if the IP is defined, then the netmask is, too.
- for tap_fd in &cfg.tap_fd {
- devs.push(create_tap_net_device(cfg, *tap_fd)?);
+ for (tap_fd, options) in &cfg.tap_fd {
+ devs.push(create_tap_net_device(cfg, *tap_fd, options)?);
}
if let (Some(host_ip), Some(netmask), Some(mac_address)) =
diff --git a/src/main.rs b/src/main.rs
index ab62f2543..e1188a86c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,13 +20,15 @@ use arch::{
set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType,
VcpuAffinity,
};
-use base::{debug, error, getpid, info, kill_process_group, reap_child, syslog, warn};
+use base::{
+ debug, error, getpid, info, kill_process_group, reap_child, syslog, warn, RawDescriptor,
+};
#[cfg(feature = "direct")]
use crosvm::DirectIoOption;
use crosvm::{
argument::{self, print_help, set_arguments, Argument},
- platform, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
- VhostUserFsOption, VhostUserOption, DISK_ID_LEN,
+ platform, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TapFdOption,
+ TouchDeviceOption, VhostUserFsOption, VhostUserOption, DISK_ID_LEN,
};
#[cfg(feature = "gpu")]
use devices::virtio::gpu::{GpuMode, GpuParameters};
@@ -1460,17 +1462,55 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
"vhost-net" => cfg.vhost_net = true,
"tap-fd" => {
- cfg.tap_fd.push(
- value
- .unwrap()
- .parse()
- .map_err(|_| argument::Error::InvalidValue {
+ let mut components = value.unwrap().split(',');
+
+ let fd: RawDescriptor =
+ components
+ .next()
+ .and_then(|x| x.parse().ok())
+ .ok_or_else(|| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from(
"this value for `tap-fd` must be an unsigned integer",
),
- })?,
- );
+ })?;
+
+ let mut mac = None;
+ for c in components {
+ let mut kv = c.splitn(2, '=');
+ let (kind, value) = match (kv.next(), kv.next()) {
+ (Some(kind), Some(value)) => (kind, value),
+ _ => {
+ return Err(argument::Error::InvalidValue {
+ value: c.to_owned(),
+ expected: String::from("option must be of the form `kind=value`"),
+ })
+ }
+ };
+ match kind {
+ "mac" => {
+ mac = Some(value.parse().map_err(|_| argument::Error::InvalidValue {
+ value: value.to_owned(),
+ expected: String::from(
+ "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
+ ),
+ })?)
+ }
+ _ => {
+ return Err(argument::Error::InvalidValue {
+ value: kind.to_owned(),
+ expected: String::from("unrecognized option"),
+ })
+ }
+ }
+ }
+ if cfg.tap_fd.contains_key(&fd) {
+ return Err(argument::Error::TooManyArguments(format!(
+ "TAP FD already used: '{}'",
+ name
+ )));
+ }
+ cfg.tap_fd.insert(fd, TapFdOption { mac });
}
#[cfg(feature = "gpu")]
"gpu" => {
@@ -1907,8 +1947,8 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
Argument::value("plugin-gid-map-file", "PATH", "Path to the file listing supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
Argument::flag("vhost-net", "Use vhost for networking."),
Argument::value("tap-fd",
- "fd",
- "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
+ "FD[,mac=MAC]",
+ "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given. MAC is the MAC address that will be set in the guest."),
#[cfg(feature = "gpu")]
Argument::flag_or_value("gpu",
"[width=INT,height=INT]",
--
2.31.1