Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
06ff46a
initial work
Mar 21, 2026
588ad0f
.
Mar 21, 2026
e12bfda
.
damanm24 Mar 22, 2026
ea942a2
Merge branch 'main' of https://github.com/microsoft/openvmm into udp-gso
damanm24 Mar 22, 2026
f5a6664
mixed up terminology
Mar 22, 2026
bd08dc9
Merge branch 'udp-gso' of https://github.com/damanm24/openvmm into ud…
Mar 22, 2026
0bc4f6d
minor cleanup
Mar 22, 2026
2cb5959
.
Mar 22, 2026
541f3fe
.
damanm24 Mar 22, 2026
83ae7f6
Merge branch 'udp-gso' of https://github.com/damanm24/openvmm into ud…
damanm24 Mar 22, 2026
648d156
copilot feedback
Mar 25, 2026
d2378dc
.
Mar 25, 2026
18730aa
.
Mar 25, 2026
b413c3e
fmt
Mar 26, 2026
21d1fba
compile fix
Mar 26, 2026
ab4d128
feedback
damanm24 Apr 9, 2026
a5c491a
Merge branch 'main' into udp-gso
damanm24 Apr 9, 2026
f660f0d
bad merge + feedback
damanm24 Apr 9, 2026
fb0e6f1
.
damanm24 Apr 9, 2026
3600e11
.
Apr 9, 2026
b4f6c3a
.
Apr 9, 2026
e244737
Merge branch 'main' into udp-gso
Apr 16, 2026
525b026
wip
damanm24 Apr 16, 2026
38755aa
Merge branch 'main' into lro
damanm24 Apr 16, 2026
f072562
Add burette test
damanm24 Apr 16, 2026
3e217d9
ethtool not in PATH
Apr 16, 2026
6dafc8e
.
Apr 17, 2026
c989e69
undo
Apr 17, 2026
c10c6e5
Merge branch 'main' into lro
Apr 17, 2026
e09c08c
Merge branch 'udp-gso' into lro
Apr 17, 2026
3a1cc49
Bring consomme up to par
Apr 23, 2026
5752cd0
Merge branch 'main' into lro
damanm24 Apr 27, 2026
0026368
review
damanm24 Apr 27, 2026
78cc2e1
Merge branch 'main' into lro
damanm24 Apr 30, 2026
69d6fa7
skip double computing the checksum
May 1, 2026
dbb5a4c
build fix
May 1, 2026
ab47b5f
review
May 1, 2026
f0c517d
fmt
May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions vm/devices/net/net_backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,17 @@ pub struct RxMetadata {
pub l4_checksum: RxChecksumState,
/// The L4 protocol.
pub l4_protocol: L4Protocol,
/// The L3 protocol (IPv4/IPv6). Used for GSO/LRO metadata.
pub l3_protocol: L3Protocol,
/// L2 (Ethernet) header length in bytes (e.g. 14, or 18 with VLAN).
pub l2_len: u8,
/// L3 (IP) header length in bytes.
pub l3_len: u16,
/// L4 (TCP/UDP) header length in bytes.
pub l4_len: u8,
/// If non-zero, this is a GSO/LRO packet and this value is the MSS
/// (maximum segment size) that should be advertised to the guest.
pub gso_size: u16,
}

impl Default for RxMetadata {
Expand All @@ -318,6 +329,11 @@ impl Default for RxMetadata {
ip_checksum: RxChecksumState::Unknown,
l4_checksum: RxChecksumState::Unknown,
l4_protocol: L4Protocol::Unknown,
l3_protocol: L3Protocol::Unknown,
l2_len: 0,
l3_len: 0,
l4_len: 0,
gso_size: 0,
}
}
}
Expand Down
85 changes: 66 additions & 19 deletions vm/devices/net/net_consomme/consomme/src/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use pal_async::driver::Driver;
use pal_async::interest::PollEvents;
use pal_async::socket::PollReady;
use pal_async::socket::PolledSocket;
use smoltcp::phy::Checksum;
use smoltcp::phy::ChecksumCapabilities;
use smoltcp::wire::ETHERNET_HEADER_LEN;
use smoltcp::wire::EthernetFrame;
Expand Down Expand Up @@ -471,7 +472,19 @@ struct Sender<'a, T> {
}

impl<T: Client> Sender<'_, T> {
fn send_packet(&mut self, tcp: &TcpRepr<'_>, payload: Option<ring::View<'_>>) {
/// Assemble and deliver a TCP packet to the client.
///
/// When `tso_mss` is `Some(mss)`, the payload is larger than a single
/// segment and the packet is delivered with [`ChecksumState::tso`] set so
/// that the downstream virtio-net device can present it to the guest as an
/// LRO/GSO packet. In this mode the TCP checksum is left as a
/// pseudo-header partial checksum (the guest completes it per-segment).
fn send_packet(
&mut self,
tcp: &TcpRepr<'_>,
payload: Option<ring::View<'_>>,
tso_mss: Option<u16>,
) {
let buffer = &mut self.state.buffer;
let mut eth_packet = EthernetFrame::new_unchecked(&mut buffer[..]);
eth_packet.set_dst_addr(self.state.params.client_mac);
Expand Down Expand Up @@ -511,22 +524,36 @@ impl<T: Client> Sender<'_, T> {
let dst_ip_addr: IpAddress = self.ft.dst.ip().into();
let src_ip_addr: IpAddress = self.ft.src.ip().into();
let mut tcp_packet = TcpPacket::new_unchecked(tcp_payload_buf);
tcp.emit(
&mut tcp_packet,
&dst_ip_addr,
&src_ip_addr,
&ChecksumCapabilities::default(),
);

// Skip the TCP checksum during emit--fill_checksum below recomputes
// it after the payload has been copied in.
let mut caps = ChecksumCapabilities::default();
caps.tcp = Checksum::None;
tcp.emit(&mut tcp_packet, &dst_ip_addr, &src_ip_addr, &caps);
// Copy payload into TCP packet
if let Some(payload) = &payload {
payload.copy_to_slice(tcp_packet.payload_mut());
}
tcp_packet.fill_checksum(&self.ft.dst.ip().into(), &self.ft.src.ip().into());

if tso_mss.is_none() {
// Normal single-segment packet: compute the full checksum.
tcp_packet.fill_checksum(&self.ft.dst.ip().into(), &self.ft.src.ip().into());
}
// For TSO packets the checksum field is left as emitted by
// smoltcp (zero / pseudo-header partial). The guest driver
// will compute per-segment checksums via NEEDS_CSUM.

let n = ETHERNET_HEADER_LEN + ip_total_len;
let checksum_state = match self.ft.dst {
SocketAddr::V4(_) => ChecksumState::TCP4,
SocketAddr::V6(_) => ChecksumState::TCP6,
let checksum_state = match (self.ft.dst, tso_mss) {
(SocketAddr::V4(_), Some(mss)) => ChecksumState {
tso: Some(mss),
..ChecksumState::TCP4
},
(SocketAddr::V6(_), Some(mss)) => ChecksumState {
tso: Some(mss),
..ChecksumState::TCP6
},
(SocketAddr::V4(_), None) => ChecksumState::TCP4,
(SocketAddr::V6(_), None) => ChecksumState::TCP6,
};

self.client.recv(&buffer[..n], &checksum_state);
Expand All @@ -550,7 +577,7 @@ impl<T: Client> Sender<'_, T> {

trace_tcp_packet(&tcp, 0, "rst xmit");

self.send_packet(&tcp, None);
self.send_packet(&tcp, None, None);
}
}

Expand Down Expand Up @@ -984,7 +1011,7 @@ impl TcpConnectionInner {
payload: &[],
};

sender.send_packet(&tcp, None);
sender.send_packet(&tcp, None, None);
self.tx_send += 1;
}

Expand Down Expand Up @@ -1023,7 +1050,9 @@ impl TcpConnectionInner {
// exceeding:
// 1. The available buffer length.
// 2. The current window.
// 3. The configured maximum segment size.
// 3. The configured maximum segment size (only when the client
// buffer is not large enough for LRO — when it is, we emit one
// large frame and let the guest segment it).
// 4. The client MTU.
let tx_segment_end = {
let ip_header_len = match sender.ft.dst {
Expand All @@ -1032,11 +1061,21 @@ impl TcpConnectionInner {
};
let header_len = ETHERNET_HEADER_LEN + ip_header_len + tcp.header_len();
let mtu = rx_mtu.min(sender.state.buffer.len());
let max_payload = mtu - header_len;
// When the client buffer can hold more than one MSS of
// payload, skip the MSS cap and fill the whole buffer —
// the packet will be delivered as an LRO/TSO frame.
// Otherwise, apply the MSS limit for normal segmentation.
let mss_limit = if max_payload > self.tx_mss {
tx_next + max_payload
} else {
tx_next + self.tx_mss
};
seq_min([
Comment thread
damanm24 marked this conversation as resolved.
tx_payload_end,
tx_window_end,
tx_next + self.tx_mss,
tx_next + (mtu - header_len),
mss_limit,
tx_next + max_payload,
])
};

Expand Down Expand Up @@ -1067,7 +1106,15 @@ impl TcpConnectionInner {
.tx_buffer
.view(payload_start..payload_start + payload_len);

sender.send_packet(&tcp, Some(payload));
// When the payload exceeds a single MSS, deliver the frame as a
// TSO/LRO packet so the guest can re-segment it.
let tso_mss = if payload_len > self.tx_mss {
Some(self.tx_mss.min(u16::MAX as usize) as u16)
} else {
None
};

sender.send_packet(&tcp, Some(payload), tso_mss);
self.tx_send = tx_next;
self.needs_ack = false;
}
Expand Down Expand Up @@ -1118,7 +1165,7 @@ impl TcpConnectionInner {

trace_tcp_packet(&tcp, 0, "ack");

sender.send_packet(&tcp, None);
sender.send_packet(&tcp, None, None);
}

fn handle_listen_syn(
Expand Down
87 changes: 78 additions & 9 deletions vm/devices/net/net_consomme/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use mesh::rpc::Rpc;
use mesh::rpc::RpcError;
use mesh::rpc::RpcSend;
use net_backend::BufferAccess;
use net_backend::L3Protocol;
use net_backend::L4Protocol;
use net_backend::QueueConfig;
use net_backend::RssConfig;
Expand Down Expand Up @@ -596,28 +597,49 @@ impl consomme::Client for Client<'_> {
};
let max = self.pool.capacity(rx_id) as usize;
if data.len() <= max {
let l4_protocol = if checksum.tcp {
L4Protocol::Tcp
} else if checksum.udp {
L4Protocol::Udp
} else {
L4Protocol::Unknown
};

// Determine L3 protocol and header lengths for GSO metadata.
// Parse the Ethernet header to find IP version, then derive
// l2_len and l3_len from the packet.
let (l3_protocol, l2_len, l3_len, l4_len) = parse_rx_header_lengths(data, checksum);

let gso_size = checksum.tso.unwrap_or(0);

Comment thread
damanm24 marked this conversation as resolved.
self.pool.write_packet(
rx_id,
&RxMetadata {
offset: 0,
len: data.len(),
ip_checksum: if checksum.ipv4 {
ip_checksum: if checksum.tso.is_some() {
// TSO packets have partial/coalesced checksums;
// the guest must recompute per-segment checksums
// via NEEDS_CSUM.
RxChecksumState::Unknown
} else if checksum.ipv4 {
RxChecksumState::Good
} else {
RxChecksumState::Unknown
},
l4_checksum: if checksum.tcp || checksum.udp {
l4_checksum: if checksum.tso.is_some() {
RxChecksumState::Unknown
} else if checksum.tcp || checksum.udp {
RxChecksumState::Good
} else {
RxChecksumState::Unknown
},
l4_protocol: if checksum.tcp {
L4Protocol::Tcp
} else if checksum.udp {
L4Protocol::Udp
} else {
L4Protocol::Unknown
},
l4_protocol,
l3_protocol,
gso_size,
l2_len,
l3_len,
l4_len,
},
data,
);
Expand All @@ -636,3 +658,50 @@ impl consomme::Client for Client<'_> {
}
}
}

/// Parse an Ethernet frame to extract L3 protocol, l2_len, l3_len, and l4_len.
///
/// Used to populate `RxMetadata` GSO fields on the receive path so that
/// the virtio-net device can construct proper virtio headers for LRO packets.
fn parse_rx_header_lengths(data: &[u8], checksum: &ChecksumState) -> (L3Protocol, u8, u16, u8) {
const ETHERTYPE_IPV4: u16 = 0x0800;
const ETHERTYPE_IPV6: u16 = 0x86DD;

if data.len() < 14 {
return (L3Protocol::Unknown, 0, 0, 0);
}

let ethertype = u16::from_be_bytes([data[12], data[13]]);
let l2_len: u8 = 14;

match ethertype {
ETHERTYPE_IPV4 if checksum.ipv4 && data.len() >= l2_len as usize + 20 => {
let ihl = (data[l2_len as usize] & 0x0f) as u16 * 4;
let l3_len = ihl.max(20);
let l4_start = l2_len as usize + l3_len as usize;
// Derive TCP header length from data offset field if TCP
let l4_len = if checksum.tcp && data.len() >= l4_start + 20 {
let data_offset = (data[l4_start + 12] >> 4) * 4;
data_offset.max(20)
} else {
Comment thread
damanm24 marked this conversation as resolved.
0
};
(L3Protocol::Ipv4, l2_len, l3_len, l4_len)
}
ETHERTYPE_IPV6 if data.len() >= l2_len as usize + 40 => {
// Base IPv6 header only. Extension headers are not parsed, but
// this is safe because consomme never generates IPv6 extension
// headers on the receive path.
let l3_len: u16 = 40;
let l4_start = l2_len as usize + l3_len as usize;
let l4_len = if checksum.tcp && data.len() >= l4_start + 20 {
let data_offset = (data[l4_start + 12] >> 4) * 4;
data_offset.max(20)
} else {
0
};
(L3Protocol::Ipv6, l2_len, l3_len, l4_len)
}
_ => (L3Protocol::Unknown, 0, 0, 0),
}
}
6 changes: 6 additions & 0 deletions vm/devices/net/net_mana/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use net_backend::BackendQueueStats;
use net_backend::BufferAccess;
use net_backend::Endpoint;
use net_backend::EndpointAction;
use net_backend::L3Protocol;
use net_backend::L4Protocol;
use net_backend::MultiQueueSupport;
use net_backend::Queue;
Expand Down Expand Up @@ -974,6 +975,11 @@ impl<T: DeviceBacking + Send> Queue for ManaQueue<T> {
ip_checksum,
l4_checksum,
l4_protocol,
l3_protocol: L3Protocol::Unknown,
l2_len: 0,
l3_len: 0,
l4_len: 0,
gso_size: 0,
},
);
if rx.bounced_len_with_padding > 0 {
Expand Down
Loading
Loading