Only attempt UDP coalescing in tun interface if USO is enabled

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet
2026-04-28 14:38:19 +02:00
parent 5aec6112bb
commit 21bf72d3d1
2 changed files with 31 additions and 10 deletions
+12 -2
View File
@@ -92,6 +92,7 @@ nix::ioctl_write_ptr_bad!(
pub struct Tun {
fd: OwnedFd,
name: String,
uso_enabled: bool,
}
impl Tun {
@@ -140,7 +141,8 @@ impl Tun {
let uso_flags = offload_flags | TUN_F_USO4 | TUN_F_USO6;
// SAFETY: file is a valid TUN fd.
let ret = unsafe { libc::ioctl(file.as_raw_fd(), TUNSETOFFLOAD, uso_flags) };
if ret == 0 {
let uso_enabled = ret == 0;
if uso_enabled {
debug!(name = %actual_name, "enabled USO offload");
}
@@ -155,6 +157,7 @@ impl Tun {
Ok(Tun {
fd: owned_fd,
name: actual_name,
uso_enabled,
})
}
@@ -260,6 +263,7 @@ impl Tun {
let write_half = WriteHalf {
fd: AsyncFd::new(write_fd)?,
write_buf: vec![0u8; READ_BUF_SIZE],
uso_enabled: self.uso_enabled,
};
Ok((read_half, write_half))
@@ -351,6 +355,8 @@ pub struct WriteHalf {
fd: AsyncFd<OwnedFd>,
/// Internal buffer for building coalesced packets (virtio_net_hdr + payload).
write_buf: Vec<u8>,
/// Whether USO (UDP Segmentation Offload) is available on this device.
uso_enabled: bool,
}
impl WriteHalf {
@@ -366,7 +372,11 @@ impl WriteHalf {
let mut remaining = pkts;
while remaining.len() > 1 {
match offload::gro_coalesce(remaining, &mut self.write_buf[VIRTIO_NET_HDR_LEN..]) {
match offload::gro_coalesce(
remaining,
&mut self.write_buf[VIRTIO_NET_HDR_LEN..],
self.uso_enabled,
) {
Ok((len, vhdr, consumed)) => {
vhdr.encode(&mut self.write_buf[..VIRTIO_NET_HDR_LEN]);
self.write_raw(&self.write_buf[..VIRTIO_NET_HDR_LEN + len])
+19 -8
View File
@@ -433,7 +433,11 @@ fn pseudo_header_acc(pkt: &[u8], info: &HeaderInfo) -> u64 {
/// If even the first two packets cannot be coalesced (incompatible flows, single packet, etc.),
/// returns an error. The caller should then fall back to writing the first packet individually
/// with a `GSO_NONE` header.
pub fn gro_coalesce(pkts: &[&[u8]], out: &mut [u8]) -> io::Result<(usize, VirtioNetHdr, usize)> {
pub fn gro_coalesce(
pkts: &[&[u8]],
out: &mut [u8],
can_uso: bool,
) -> io::Result<(usize, VirtioNetHdr, usize)> {
if pkts.len() < 2 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
@@ -446,6 +450,13 @@ pub fn gro_coalesce(pkts: &[&[u8]], out: &mut [u8]) -> io::Result<(usize, Virtio
let is_tcp = info.protocol == IPPROTO_TCP;
if !is_tcp && !can_uso {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"USO not available for UDP coalescing",
));
}
// Extract flow key from first packet.
let flow = FlowKey::from_packet(first, &info)?;
@@ -876,7 +887,7 @@ mod tests {
let pkts: Vec<&[u8]> = vec![&pkt0, &pkt1];
let mut out = vec![0u8; 65536];
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out).unwrap();
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out, true).unwrap();
assert_eq!(consumed, 2);
assert_eq!(vhdr.gso_type, VIRTIO_NET_HDR_GSO_TCPV4);
@@ -909,7 +920,7 @@ mod tests {
let pkts: Vec<&[u8]> = vec![&pkt0, &pkt1];
let mut out = vec![0u8; 65536];
assert!(gro_coalesce(&pkts, &mut out).is_err());
assert!(gro_coalesce(&pkts, &mut out, true).is_err());
}
#[test]
@@ -936,7 +947,7 @@ mod tests {
let pkts: Vec<&[u8]> = vec![&pkt0, &pkt1];
let mut out = vec![0u8; 65536];
assert!(gro_coalesce(&pkts, &mut out).is_err());
assert!(gro_coalesce(&pkts, &mut out, true).is_err());
}
#[test]
@@ -974,7 +985,7 @@ mod tests {
// Coalesce.
let pkts: Vec<&[u8]> = vec![&pkt0, &pkt1, &pkt2];
let mut coalesced = vec![0u8; 65536];
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut coalesced).unwrap();
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut coalesced, true).unwrap();
assert_eq!(consumed, 3);
// Build raw buffer as if read from kernel.
@@ -1049,7 +1060,7 @@ mod tests {
let pkts: Vec<&[u8]> = vec![&pkt0, &pkt1];
let mut out = vec![0u8; 65536];
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out).unwrap();
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out, true).unwrap();
assert_eq!(consumed, 2);
assert_eq!(vhdr.gso_type, VIRTIO_NET_HDR_GSO_UDP_L4);
@@ -1102,7 +1113,7 @@ mod tests {
let out_size = header_len + PAYLOAD_SIZE * 2 + 10; // just enough for 2
let mut out = vec![0u8; out_size];
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out).unwrap();
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out, true).unwrap();
assert_eq!(consumed, 2);
assert_eq!(vhdr.gso_type, VIRTIO_NET_HDR_GSO_UDP_L4);
@@ -1145,7 +1156,7 @@ mod tests {
let pkts: Vec<&[u8]> = vec![&pkt0, &pkt1, &pkt2];
let mut out = vec![0u8; 65536];
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out).unwrap();
let (len, vhdr, consumed) = gro_coalesce(&pkts, &mut out, true).unwrap();
// Should coalesce only the first 2 packets.
assert_eq!(consumed, 2);