Visitar URL original
virtio/net: Advertise MTU to the guest via VIRTIO_NET_F_MTU · firecracker-microvm/firecracker@63ad995 · GitHub
Skip to content

Commit 63ad995

Browse files
committed
virtio/net: Advertise MTU to the guest via VIRTIO_NET_F_MTU
Add MTU advertisment support to the virtio-net device. The tap interface MTU is read using SIOCGIFMTU and stored in the ConfigSpace alongside the MAC address, with the intermediate padding fields (_status, _max_virtqueue_pairs) added to match the virtio_net_config layout. VIRTIO_NET_F_MTU is advertised in the available features so the guest driver can read the MTU from the config space and configure the network interface accordingly, avoiding fragmentation or oversized-packet issues when the underlying tap MTU differs from the default. Signed-off-by: Anastassios Nanos <ananos@nubificus.co.uk>
1 parent a481d51 commit 63ad995

5 files changed

Lines changed: 101 additions & 8 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ and this project adheres to
2424
logging resumes after suppression, a warn-level summary reports the count of
2525
suppressed messages. A new `rate_limited_log_count` metric tracks the total
2626
number of suppressed messages.
27+
- [#5828](https://github.com/firecracker-microvm/firecracker/pull/5828):
28+
Advertise TAP interface MTU to the guest via `VIRTIO_NET_F_MTU`. The
29+
virtio-net device now reads the host TAP MTU using `SIOCGIFMTU` and stores it
30+
in the config space, allowing the guest driver to configure the network
31+
interface with the correct MTU automatically.
2732

2833
### Changed
2934

‎src/vmm/src/devices/virtio/net/device.rs‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::devices::virtio::generated::virtio_config::VIRTIO_F_VERSION_1;
2222
use crate::devices::virtio::generated::virtio_net::{
2323
VIRTIO_NET_F_CSUM, VIRTIO_NET_F_GUEST_CSUM, VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6,
2424
VIRTIO_NET_F_GUEST_UFO, VIRTIO_NET_F_HOST_TSO4, VIRTIO_NET_F_HOST_TSO6, VIRTIO_NET_F_HOST_UFO,
25-
VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, virtio_net_hdr_v1,
25+
VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU, virtio_net_hdr_v1,
2626
};
2727
use crate::devices::virtio::generated::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
2828
use crate::devices::virtio::iovec::{
@@ -88,9 +88,17 @@ fn init_vnet_hdr(buf: &mut [u8]) {
8888
#[repr(C)]
8989
pub struct ConfigSpace {
9090
pub guest_mac: MacAddr,
91+
// Padding fields to match the virtio_net_config layout:
92+
// offset 6: status (u16, not advertised)
93+
_status: u16,
94+
// offset 8: max_virtqueue_pairs (u16, not advertised)
95+
_max_virtqueue_pairs: u16,
96+
// offset 10: mtu (u16, advertised via VIRTIO_NET_F_MTU)
97+
pub mtu: u16,
9198
}
9299

93100
// SAFETY: `ConfigSpace` contains only PODs in `repr(C)` or `repr(transparent)`, without padding.
101+
// MacAddr is [u8; 6] (align 1, size 6); u16 fields follow at naturally aligned offsets.
94102
unsafe impl ByteValued for ConfigSpace {}
95103

96104
#[derive(Debug, thiserror::Error, displaydoc::Display)]
@@ -275,6 +283,7 @@ impl Net {
275283
guest_mac: Option<MacAddr>,
276284
rx_rate_limiter: RateLimiter,
277285
tx_rate_limiter: RateLimiter,
286+
mtu: u16,
278287
) -> Result<Self, NetError> {
279288
let mut avail_features = (1 << VIRTIO_NET_F_GUEST_CSUM)
280289
| (1 << VIRTIO_NET_F_CSUM)
@@ -286,9 +295,13 @@ impl Net {
286295
| (1 << VIRTIO_NET_F_HOST_UFO)
287296
| (1 << VIRTIO_F_VERSION_1)
288297
| (1 << VIRTIO_NET_F_MRG_RXBUF)
289-
| (1 << VIRTIO_RING_F_EVENT_IDX);
298+
| (1 << VIRTIO_RING_F_EVENT_IDX)
299+
| (1 << VIRTIO_NET_F_MTU);
290300

291-
let mut config_space = ConfigSpace::default();
301+
let mut config_space = ConfigSpace {
302+
mtu,
303+
..Default::default()
304+
};
292305
if let Some(mac) = guest_mac {
293306
config_space.guest_mac = mac;
294307
// Enabling feature for MAC address configuration
@@ -339,7 +352,9 @@ impl Net {
339352
tap.set_vnet_hdr_size(vnet_hdr_size)
340353
.map_err(NetError::TapSetVnetHdrSize)?;
341354

342-
Self::new_with_tap(id, tap, guest_mac, rx_rate_limiter, tx_rate_limiter)
355+
let mtu = tap.mtu().map_err(NetError::TapGetMtu)?;
356+
357+
Self::new_with_tap(id, tap, guest_mac, rx_rate_limiter, tx_rate_limiter, mtu)
343358
}
344359

345360
/// Provides the MAC of this net device.
@@ -1184,9 +1199,9 @@ pub mod tests {
11841199
net.read_config(0, &mut config_mac);
11851200
assert_eq!(&config_mac, mac.get_bytes());
11861201

1187-
// Invalid read.
1202+
// Invalid read (past end of config space).
11881203
config_mac = [0u8; MAC_ADDR_LEN as usize];
1189-
net.read_config(u64::from(MAC_ADDR_LEN), &mut config_mac);
1204+
net.read_config(std::mem::size_of::<ConfigSpace>() as u64, &mut config_mac);
11901205
assert_eq!(config_mac, [0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
11911206
}
11921207

@@ -1214,8 +1229,8 @@ pub mod tests {
12141229
net.read_config(0, &mut new_config_read);
12151230
assert_eq!(new_config, new_config_read);
12161231

1217-
// Invalid write.
1218-
net.write_config(5, &new_config);
1232+
// Invalid write (past end of config space).
1233+
net.write_config(std::mem::size_of::<ConfigSpace>() as u64, &new_config);
12191234
// Verify old config was untouched.
12201235
new_config_read = [0u8; MAC_ADDR_LEN as usize];
12211236
net.read_config(0, &mut new_config_read);

‎src/vmm/src/devices/virtio/net/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub enum NetError {
4949
TapOpen(TapError),
5050
/// Setting vnet header size failed: {0}
5151
TapSetVnetHdrSize(TapError),
52+
/// Getting tap MTU failed: {0}
53+
TapGetMtu(TapError),
5254
/// EventFd error: {0}
5355
EventFd(io::Error),
5456
/// IO error: {0}

‎src/vmm/src/devices/virtio/net/tap.rs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ pub enum TapError {
3535
SetOffloadFlags(IoError),
3636
/// Error while setting size of the vnet header: {0}
3737
SetSizeOfVnetHdr(IoError),
38+
/// Error while creating a socket: {0}
39+
CreateSocket(IoError),
40+
/// Error while getting MTU: {0}
41+
GetMtu(IoError),
42+
/// MTU value out of range
43+
MtuOutOfRange,
3844
}
3945

4046
const TUNTAP: ::std::os::raw::c_uint = 84;
@@ -180,6 +186,26 @@ impl Tap {
180186
Ok(())
181187
}
182188

189+
/// Get the MTU of the tap interface.
190+
pub fn mtu(&self) -> Result<u16, TapError> {
191+
// SAFETY: socket() is safe to call with valid parameters.
192+
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
193+
if sock < 0 {
194+
return Err(TapError::CreateSocket(IoError::last_os_error()));
195+
}
196+
// SAFETY: sock is a valid fd; we own it and close it via File.
197+
let sock_file = unsafe { File::from_raw_fd(sock) };
198+
199+
let ifreq = IfReqBuilder::new()
200+
.if_name(&self.if_name)
201+
.execute(&sock_file, generated::sockios::SIOCGIFMTU as u64)
202+
.map_err(TapError::GetMtu)?;
203+
204+
// SAFETY: After a successful SIOCGIFMTU ioctl the ifru_mtu field is valid.
205+
let mtu = unsafe { ifreq.ifr_ifru.ifru_mtu };
206+
u16::try_from(mtu).map_err(|_| TapError::MtuOutOfRange)
207+
}
208+
183209
/// Write an `IoVecBuffer` to tap
184210
pub(crate) fn write_iovec(&mut self, buffer: &IoVecBuffer) -> Result<usize, IoError> {
185211
let iovcnt = i32::try_from(buffer.iovec_count()).unwrap();

‎tests/integration_tests/functional/test_net.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99
from tenacity import Retrying, stop_after_attempt, wait_fixed
1010

11+
import host_tools.network as net_tools # pylint: disable=import-error
1112
from framework import utils
1213

1314
# The iperf version to run this tests with
@@ -127,3 +128,47 @@ def test_tap_offload(uvm_any):
127128
with attempt:
128129
ret = vm.ssh.check_output(f"sync; cat {out_filename}")
129130
assert ret.stdout == message, f"{ret.stdout=} {ret.stderr=}"
131+
132+
133+
def test_tap_mtu_advertised_to_guest(uvm_plain_any):
134+
"""
135+
Verify that VIRTIO_NET_F_MTU correctly advertises the TAP MTU to the guest.
136+
137+
Configures multiple TAP interfaces with distinct MTU values and checks that
138+
each guest network interface reports the MTU matching its host TAP.
139+
"""
140+
vm = uvm_plain_any
141+
vm.spawn()
142+
vm.basic_config()
143+
144+
# (interface index, MTU) pairs with varied values
145+
iface_mtus = [(0, 1450), (1, 1500), (2, 8935)]
146+
147+
for idx, mtu in iface_mtus:
148+
iface = net_tools.NetIfaceConfig.with_id(idx)
149+
# Create the tap without registering with Firecracker yet, so we can
150+
# set the MTU before Firecracker reads it (MTU is read at device init).
151+
vm.add_net_iface(iface, api=False)
152+
vm.netns.check_output(f"ip link set {iface.tap_name} mtu {mtu}")
153+
# Register with Firecracker; tap.mtu() is called during this API call.
154+
vm.api.network.put(
155+
iface_id=iface.dev_name,
156+
host_dev_name=iface.tap_name,
157+
guest_mac=iface.guest_mac,
158+
)
159+
160+
vm.start()
161+
162+
# Verify each guest interface carries the expected MTU.
163+
# SSH runs over eth0; we can still query other interfaces from there.
164+
for idx, mtu in iface_mtus:
165+
iface_name = f"eth{idx}"
166+
guest_ip = vm.iface[iface_name]["iface"].guest_ip
167+
guest_if = net_tools.get_guest_net_if_name(vm.ssh, guest_ip)
168+
assert (
169+
guest_if is not None
170+
), f"Could not find guest interface for {iface_name} ({guest_ip})"
171+
_, stdout, _ = vm.ssh.check_output(f"cat /sys/class/net/{guest_if}/mtu")
172+
assert (
173+
int(stdout.strip()) == mtu
174+
), f"{iface_name} (guest: {guest_if}): expected MTU {mtu}, got {stdout.strip()}"

0 commit comments

Comments
 (0)