diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index 47136cc4d..20dc1a9a6 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -131,17 +131,19 @@ pub fn build_net_config_space( config: &mut VirtioNetConfig, mac: MacAddr, num_queues: usize, + mtu: Option, avail_features: &mut u64, ) { config.mac.copy_from_slice(mac.get_bytes()); *avail_features |= 1 << VIRTIO_NET_F_MAC; - build_net_config_space_with_mq(config, num_queues, avail_features); + build_net_config_space_with_mq(config, num_queues, mtu, avail_features); } pub fn build_net_config_space_with_mq( config: &mut VirtioNetConfig, num_queues: usize, + mtu: Option, avail_features: &mut u64, ) { let num_queue_pairs = (num_queues / 2) as u16; @@ -151,6 +153,9 @@ pub fn build_net_config_space_with_mq( config.max_virtqueue_pairs = num_queue_pairs; *avail_features |= 1 << VIRTIO_NET_F_MQ; } + if let Some(mtu) = mtu { + config.mtu = mtu; + } } pub fn virtio_features_to_tap_offload(features: u64) -> c_uint { diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index 431ac8c17..6ddbe67ea 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -30,6 +30,8 @@ pub enum Error { TapGetMac(TapError), #[error("Setting vnet header size failed: {0}")] TapSetVnetHdrSize(TapError), + #[error("Setting MTU failed: {0}")] + TapSetMtu(TapError), #[error("Enabling tap interface failed: {0}")] TapEnable(TapError), } @@ -63,6 +65,7 @@ pub fn open_tap( ip_addr: Option, netmask: Option, host_mac: &mut Option, + mtu: Option, num_rx_q: usize, flags: Option, ) -> Result> { @@ -95,6 +98,9 @@ pub fn open_tap( } else { *host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?) } + 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_size) diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index b212fb963..f98459f00 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -297,6 +297,37 @@ impl Tap { Ok(()) } + pub fn mtu(&self) -> Result { + let sock = create_unix_socket().map_err(Error::NetUtil)?; + + let ifreq = self.get_ifreq(); + + // ioctl is safe. Called with a valid sock fd, and we check the return. + let ret = unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFMTU as c_ulong, &ifreq) }; + if ret < 0 { + return Err(Error::IoctlError(IoError::last_os_error())); + } + + let mtu = unsafe { ifreq.ifr_ifru.ifru_mtu }; + + Ok(mtu) + } + + pub fn set_mtu(&self, mtu: i32) -> Result<()> { + let sock = create_unix_socket().map_err(Error::NetUtil)?; + + let mut ifreq = self.get_ifreq(); + ifreq.ifr_ifru.ifru_mtu = mtu; + + // ioctl is safe. Called with a valid sock fd, and we check the return. + let ret = unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFMTU as c_ulong, &ifreq) }; + if ret < 0 { + return Err(Error::IoctlError(IoError::last_os_error())); + } + + Ok(()) + } + /// Set the offload flags for the tap interface. pub fn set_offload(&self, flags: c_uint) -> Result<()> { // ioctl is safe. Called with a valid tap fd, and we check the return. diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index c6c9d8f13..08d051fdb 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -796,6 +796,13 @@ impl Guest { ) } + pub fn default_net_string_w_mtu(&self, mtu: u16) -> String { + format!( + "tap=,mac={},ip={},mask=255.255.255.0,mtu={}", + self.network.guest_mac, self.network.host_ip, mtu + ) + } + pub fn ssh_command(&self, command: &str) -> Result { ssh_command_ip( command, diff --git a/tests/integration.rs b/tests/integration.rs index 74392d482..b84db1b74 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2411,9 +2411,9 @@ mod common_parallel { .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args(["--net", guest.default_net_string_w_mtu(3000).as_str()]) .capture_output() - .default_disks() - .default_net(); + .default_disks(); let mut child = cmd.spawn().unwrap(); @@ -2434,6 +2434,13 @@ mod common_parallel { .trim(), "success" ); + assert_eq!( + guest + .ssh_command(format!("cat /sys/class/net/{}/mtu", iface).as_str()) + .unwrap() + .trim(), + "3000" + ); }); let _ = child.kill(); @@ -6098,6 +6105,7 @@ mod common_parallel { Some(std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap()), None, &mut None, + None, num_queue_pairs, Some(libc::O_RDWR | libc::O_NONBLOCK), ) diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index e4925e30a..76c2649a9 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -133,6 +133,7 @@ impl VhostUserNetBackend { Some(ip_addr), Some(netmask), &mut Some(host_mac), + None, num_queues / 2, None, ) diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 63e2210e7..02d9d9519 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -49,6 +49,9 @@ use vmm_sys_util::eventfd::EventFd; // Event available on the control queue. const CTRL_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1; +// Following the VIRTIO specification, the MTU should be at least 1280. +pub const MIN_MTU: u16 = 1280; + pub struct NetCtrlEpollHandler { pub mem: GuestMemoryAtomic, pub kill_evt: EventFd, @@ -428,7 +431,7 @@ impl VersionMapped for NetState {} impl Net { /// Create a new virtio network device with the given TAP interface. #[allow(clippy::too_many_arguments)] - pub fn new_with_tap( + fn new_with_tap( id: String, taps: Vec, guest_mac: Option, @@ -439,6 +442,11 @@ impl Net { rate_limiter_config: Option, exit_evt: EventFd, ) -> Result { + let mut mtu = None; + if !taps.is_empty() { + mtu = Some(taps[0].mtu().map_err(Error::TapError)? as u16); + } + let mut avail_features = 1 << VIRTIO_NET_F_CSUM | 1 << VIRTIO_NET_F_CTRL_GUEST_OFFLOADS | 1 << VIRTIO_NET_F_GUEST_CSUM @@ -453,6 +461,9 @@ impl Net { | 1 << VIRTIO_RING_F_EVENT_IDX | 1 << VIRTIO_F_VERSION_1; + if mtu.is_some() { + avail_features |= 1u64 << VIRTIO_NET_F_MTU; + } if iommu { avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; } @@ -462,9 +473,9 @@ impl Net { let mut config = VirtioNetConfig::default(); if let Some(mac) = guest_mac { - build_net_config_space(&mut config, mac, num_queues, &mut avail_features); + build_net_config_space(&mut config, mac, num_queues, mtu, &mut avail_features); } else { - build_net_config_space_with_mq(&mut config, num_queues, &mut avail_features); + build_net_config_space_with_mq(&mut config, num_queues, mtu, &mut avail_features); } Ok(Net { @@ -497,6 +508,7 @@ impl Net { netmask: Option, guest_mac: Option, host_mac: &mut Option, + mtu: Option, iommu: bool, num_queues: usize, queue_size: u16, @@ -504,8 +516,16 @@ impl Net { rate_limiter_config: Option, exit_evt: EventFd, ) -> Result { - let taps = open_tap(if_name, ip_addr, netmask, host_mac, num_queues / 2, None) - .map_err(Error::OpenTap)?; + let taps = open_tap( + if_name, + ip_addr, + netmask, + host_mac, + mtu, + num_queues / 2, + None, + ) + .map_err(Error::OpenTap)?; Self::new_with_tap( id, diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index 93cf195fd..c95661ac3 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -24,7 +24,7 @@ use virtio_bindings::bindings::virtio_net::{ VIRTIO_NET_F_CSUM, VIRTIO_NET_F_CTRL_VQ, VIRTIO_NET_F_GUEST_CSUM, VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6, VIRTIO_NET_F_GUEST_UFO, VIRTIO_NET_F_HOST_ECN, VIRTIO_NET_F_HOST_TSO4, VIRTIO_NET_F_HOST_TSO6, VIRTIO_NET_F_HOST_UFO, - VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, + VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU, }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_queue::{Queue, QueueT}; @@ -70,6 +70,7 @@ impl Net { pub fn new( id: String, mac_addr: MacAddr, + mtu: Option, vu_cfg: VhostUserConfig, server: bool, seccomp_action: SeccompAction, @@ -126,8 +127,12 @@ impl Net { | 1 << VIRTIO_F_VERSION_1 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + if mtu.is_some() { + avail_features |= 1u64 << VIRTIO_NET_F_MTU; + } + let mut config = VirtioNetConfig::default(); - build_net_config_space(&mut config, mac_addr, num_queues, &mut avail_features); + build_net_config_space(&mut config, mac_addr, num_queues, mtu, &mut avail_features); let mut vu = VhostUserHandle::connect_vhost_user(server, &vu_cfg.socket, num_queues as u64, false)?; diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 514aad63e..457cfd9cc 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -819,6 +819,11 @@ components: default: "255.255.255.0" mac: type: string + host_mac: + type: string + mtu: + type: integer + default: 1280 iommu: type: boolean default: false diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 2810c4ad4..814dfc97b 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -180,6 +180,8 @@ pub enum ValidationError { IommuNotSupported, /// Duplicated device path (device added twice) DuplicateDevicePath(String), + /// Provided MTU is lower than what the VIRTIO specification expects + InvalidMtu(u16), } type ValidationResult = std::result::Result; @@ -280,6 +282,13 @@ impl fmt::Display for ValidationError { write!(f, "Device does not support being placed behind IOMMU") } DuplicateDevicePath(p) => write!(f, "Duplicated device path: {}", p), + &InvalidMtu(mtu) => { + write!( + f, + "Provided MTU {} is lower than 1280 (expected by VIRTIO specification)", + mtu + ) + } } } } @@ -1238,6 +1247,8 @@ pub struct NetConfig { pub mac: MacAddr, #[serde(default)] pub host_mac: Option, + #[serde(default = "default_netconfig_mtu")] + pub mtu: u16, #[serde(default)] pub iommu: bool, #[serde(default = "default_netconfig_num_queues")] @@ -1275,6 +1286,10 @@ fn default_netconfig_mac() -> MacAddr { MacAddr::local_random() } +fn default_netconfig_mtu() -> u16 { + virtio_devices::net::MIN_MTU +} + fn default_netconfig_num_queues() -> usize { DEFAULT_NUM_QUEUES_VUNET } @@ -1291,6 +1306,7 @@ impl Default for NetConfig { mask: default_netconfig_mask(), mac: default_netconfig_mac(), host_mac: None, + mtu: default_netconfig_mtu(), iommu: false, num_queues: default_netconfig_num_queues(), queue_size: default_netconfig_queue_size(), @@ -1322,6 +1338,7 @@ impl NetConfig { .add("mask") .add("mac") .add("host_mac") + .add("mtu") .add("iommu") .add("queue_size") .add("num_queues") @@ -1353,6 +1370,10 @@ impl NetConfig { .map_err(Error::ParseNetwork)? .unwrap_or_else(default_netconfig_mac); let host_mac = parser.convert("host_mac").map_err(Error::ParseNetwork)?; + let mtu = parser + .convert("mtu") + .map_err(Error::ParseNetwork)? + .unwrap_or_else(default_netconfig_mtu); let iommu = parser .convert::("iommu") .map_err(Error::ParseNetwork)? @@ -1442,6 +1463,7 @@ impl NetConfig { mask, mac, host_mac, + mtu, iommu, num_queues, queue_size, @@ -1493,6 +1515,10 @@ impl NetConfig { } } + if self.mtu < virtio_devices::net::MIN_MTU { + return Err(ValidationError::InvalidMtu(self.mtu)); + } + Ok(()) } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 672e4cb00..edd065537 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2241,6 +2241,7 @@ impl DeviceManager { match virtio_devices::vhost_user::Net::new( id.clone(), net_cfg.mac, + Some(net_cfg.mtu), vu_cfg, server, self.seccomp_action.clone(), @@ -2271,6 +2272,7 @@ impl DeviceManager { None, Some(net_cfg.mac), &mut net_cfg.host_mac, + None, self.force_iommu | net_cfg.iommu, net_cfg.num_queues, net_cfg.queue_size, @@ -2307,6 +2309,7 @@ impl DeviceManager { Some(net_cfg.mask), Some(net_cfg.mac), &mut net_cfg.host_mac, + Some(net_cfg.mtu), self.force_iommu | net_cfg.iommu, net_cfg.num_queues, net_cfg.queue_size, diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 4fb46ed14..ac8629f2a 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -66,6 +66,8 @@ const SIOCGIFFLAGS: u64 = 0x8913; const SIOCGIFHWADDR: u64 = 0x8927; const SIOCSIFFLAGS: u64 = 0x8914; const SIOCSIFADDR: u64 = 0x8916; +const SIOCGIFMTU: u64 = 0x8921; +const SIOCSIFMTU: u64 = 0x8922; const SIOCSIFHWADDR: u64 = 0x8924; const SIOCSIFNETMASK: u64 = 0x891c; @@ -252,9 +254,11 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, FIONBIO)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFFLAGS)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFHWADDR)?], + and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFMTU)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFADDR)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFFLAGS)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFHWADDR)?], + and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFMTU)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFNETMASK)?], and![Cond::new(1, ArgLen::Dword, Eq, TCSETS)?], and![Cond::new(1, ArgLen::Dword, Eq, TCGETS)?],