Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: support for TCP Segmentation Offloading (TSO) #830

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 24 additions & 10 deletions src/iface/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,16 @@ impl InterfaceInner {
self.caps.ip_mtu()
}

#[allow(unused)] // unused depending on which sockets are enabled
pub(crate) fn tso4(&self) -> bool {
self.caps.tso4()
}

#[allow(unused)] // unused depending on which sockets are enabled
pub(crate) fn tso6(&self) -> bool {
self.caps.tso6()
}

#[allow(unused)] // unused depending on which sockets are enabled, and in tests
pub(crate) fn rand(&mut self) -> &mut Rand {
&mut self.rand
Expand Down Expand Up @@ -1635,7 +1645,7 @@ impl InterfaceInner {
#[cfg(feature = "proto-ipv4")]
IpRepr::Ipv4(repr) => {
// If we have an IPv4 packet, then we need to check if we need to fragment it.
if total_ip_len > self.caps.max_transmission_unit {
if total_ip_len > self.caps.max_transmission_unit && !self.caps.tso.tso4() {
#[cfg(feature = "proto-ipv4-fragmentation")]
{
net_debug!("start fragmentation");
Expand Down Expand Up @@ -1728,16 +1738,20 @@ impl InterfaceInner {
}
// We don't support IPv6 fragmentation yet.
#[cfg(feature = "proto-ipv6")]
IpRepr::Ipv6(_) => tx_token.consume(total_len, |mut tx_buffer| {
#[cfg(feature = "medium-ethernet")]
if matches!(self.caps.medium, Medium::Ethernet) {
emit_ethernet(&ip_repr, tx_buffer)?;
tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
}
IpRepr::Ipv6(_) => {
tx_token.set_meta(meta);

emit_ip(&ip_repr, tx_buffer);
Ok(())
}),
tx_token.consume(total_len, |mut tx_buffer| {
#[cfg(feature = "medium-ethernet")]
if matches!(self.caps.medium, Medium::Ethernet) {
emit_ethernet(&ip_repr, tx_buffer)?;
tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
}

emit_ip(&ip_repr, tx_buffer);
Ok(())
})
}
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions src/phy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,39 @@ impl ChecksumCapabilities {
}
}

/// Describes if the device supports TCP segment offloading (TSO).
stlankes marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Copy, Clone, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TsoCapabilities {
/// Supports TSO for IPv4 and IPv6
Both,
/// Supports TSO for IPv4
Tso4,
/// Supports TSO for IPv6
Tso6,
/// Doesn't support TSO
#[default]
None,
}

impl TsoCapabilities {
/// Returns true if TCP segment offloading is supported for IPv4
pub fn tso4(&self) -> bool {
match *self {
TsoCapabilities::Both | TsoCapabilities::Tso4 => true,
_ => false,
}
}

/// Returns true if TCP segment offloading is supported for IPv6
pub fn tso6(&self) -> bool {
match *self {
TsoCapabilities::Both | TsoCapabilities::Tso6 => true,
_ => false,
}
}
}

/// A description of device capabilities.
///
/// Higher-level protocols may achieve higher throughput or lower latency if they consider
Expand Down Expand Up @@ -267,6 +300,12 @@ pub struct DeviceCapabilities {
/// If the network device is capable of verifying or computing checksums for some protocols,
/// it can request that the stack not do so in software to improve performance.
pub checksum: ChecksumCapabilities,

/// TCP segmentation offload (TSO) support
///
/// If the network device is able to support TCP segmentation offloading (TSO),
/// the stack forward the segementation to the harware to improve qthe performance.
stlankes marked this conversation as resolved.
Show resolved Hide resolved
pub tso: TsoCapabilities,
}

impl DeviceCapabilities {
Expand All @@ -282,6 +321,14 @@ impl DeviceCapabilities {
Medium::Ieee802154 => self.max_transmission_unit, // TODO(thvdveld): what is the MTU for Medium::IEEE802
}
}

pub fn tso4(&self) -> bool {
self.tso.tso4()
}

pub fn tso6(&self) -> bool {
self.tso.tso6()
}
}

/// Type of medium of a device.
Expand Down
39 changes: 29 additions & 10 deletions src/socket/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use crate::socket::{Context, PollAt};
use crate::storage::{Assembler, RingBuffer};
use crate::time::{Duration, Instant};
use crate::wire::{
IpAddress, IpEndpoint, IpListenEndpoint, IpProtocol, IpRepr, TcpControl, TcpRepr, TcpSeqNumber,
TCP_HEADER_LEN,
ip::Version, IpAddress, IpEndpoint, IpListenEndpoint, IpProtocol, IpRepr, TcpControl, TcpRepr,
TcpSeqNumber, ETHERNET_HEADER_LEN, TCP_HEADER_LEN,
};

macro_rules! tcp_trace {
Expand Down Expand Up @@ -1868,7 +1868,10 @@ impl<'a> Socket<'a> {
let assembler_was_empty = self.assembler.is_empty();

// Try adding payload octets to the assembler.
let Ok(contig_len) = self.assembler.add_then_remove_front(payload_offset, payload_len) else {
let Ok(contig_len) = self
.assembler
.add_then_remove_front(payload_offset, payload_len)
else {
net_debug!(
"assembler: too many holes to add {} octets at offset {}",
payload_len,
Expand Down Expand Up @@ -2191,13 +2194,29 @@ impl<'a> Socket<'a> {
0
};

// Maximum size we're allowed to send. This can be limited by 3 factors:
// 1. remote window
// 2. MSS the remote is willing to accept, probably determined by their MTU
// 3. MSS we can send, determined by our MTU.
let size = win_limit
.min(self.remote_mss)
.min(cx.ip_mtu() - ip_repr.header_len() - TCP_HEADER_LEN);
let tso = match ip_repr.version() {
#[cfg(feature = "proto-ipv4")]
Version::Ipv4 => cx.tso4(),
#[cfg(feature = "proto-ipv6")]
Version::Ipv6 => cx.tso6(),
};

let size = if tso {
win_limit.min(
u16::MAX as usize
- ETHERNET_HEADER_LEN
- ip_repr.header_len()
- TCP_HEADER_LEN,
)
} else {
// Maximum size we're allowed to send. This can be limited by 3 factors:
// 1. remote window
// 2. MSS the remote is willing to accept, probably determined by their MTU
// 3. MSS we can send, determined by our MTU.
win_limit
.min(self.remote_mss)
.min(cx.ip_mtu() - ip_repr.header_len() - TCP_HEADER_LEN)
};

let offset = self.remote_last_seq - self.local_seq_no;
repr.payload = self.tx_buffer.get_allocated(offset, size);
Expand Down