Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion src/stream/tcb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub(crate) struct Tcb {
seq: SeqNum,
ack: SeqNum,
mtu: u16,
/// The largest segment the peer announced in the MSS option of its SYN, unbounded without one.
peer_mss: u16,
last_received_ack: SeqNum,
send_window: u32,
send_window_shift: u8,
Expand Down Expand Up @@ -125,6 +127,7 @@ impl Tcb {
seq: seq.into(),
ack,
mtu,
peer_mss: u16::MAX,
last_received_ack: seq.into(),
send_window: peer_window as u32,
send_window_shift,
Expand All @@ -144,10 +147,19 @@ impl Tcb {
}
}

/// RFC 9293 §3.7.1: a sender never sends a segment larger than the peer announced. Below 88,
/// the floor Linux applies (TCP_MIN_MSS), the option is not followed, so a peer cannot ask for
/// its data a few bytes at a time.
pub(super) fn set_peer_mss(&mut self, mss: u16) {
self.peer_mss = mss.max(88);
}

pub fn calculate_payload_max_len(&self, ip_header_size: usize, tcp_header_size: usize) -> usize {
let send_window = self.get_send_window() as usize;
let mtu = self.get_mtu() as usize;
std::cmp::min(send_window, mtu.saturating_sub(ip_header_size + tcp_header_size))
// The MSS counts the payload and the TCP options of a segment, but not the 20-byte base header.
let mss = (self.peer_mss as usize).saturating_sub(tcp_header_size.saturating_sub(20));
send_window.min(mtu.saturating_sub(ip_header_size + tcp_header_size)).min(mss)
}

pub fn update_duplicate_ack_count(&mut self, rcvd_ack: SeqNum) {
Expand Down
32 changes: 31 additions & 1 deletion src/stream/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl IpStackTcpStream {
})
})
.flatten();
let tcb = Tcb::new(
let mut tcb = Tcb::new(
SeqNum(tcp.sequence_number),
tcp.window_size,
peer_window_shift,
Expand All @@ -207,6 +207,15 @@ impl IpStackTcpStream {
config.rto,
config.max_retransmit_count,
);
// Like the window scale, the MSS option is read only from a segment carrying SYN.
if tcp.syn
&& let Some(mss) = tcp.options_iterator().flatten().find_map(|option| match option {
TcpOptionElement::MaximumSegmentSize(mss) => Some(mss),
_ => None,
})
{
tcb.set_peer_mss(mss);
}
let tuple = NetworkTuple::new(src_addr, dst_addr, true);
if !tcp.syn {
if !tcp.rst
Expand Down Expand Up @@ -1575,6 +1584,27 @@ mod tests {
assert_eq!(tcb.get_send_window(), 40_000 << 14);
}

/// RFC 9293 §3.7.1: a segment never carries more payload than the MSS the peer announced in its
/// SYN, and an announced MSS below 88 (Linux's TCP_MIN_MSS) is read as 88.
#[tokio::test]
async fn segments_stay_within_the_peers_mss() {
for (announced, expected) in [(600, 600), (100, 100), (40, 88)] {
let (mut stream, syn_ack, mut up_rx) = open(u16::MAX, &[TcpOptionElement::MaximumSegmentSize(announced)]).await;
let peer_seq = SeqNum(syn_ack.acknowledgment_number);
feed(&stream, &syn_ack, peer_seq, u16::MAX, &[1; 4]);
up_rx.recv().await.unwrap();

let (_, waker) = recording_waker();
let mut cx = Context::from_waker(&waker);
let written = match Pin::new(&mut stream).poll_write(&mut cx, &[7; 1460]) {
Poll::Ready(Ok(n)) => n,
other => panic!("the write did not complete: {other:?}"),
};
assert_eq!(written, expected, "peer MSS {announced}: wrong write size");
assert_eq!(up_rx.recv().await.unwrap().payload.map(|p| p.len()), Some(expected));
}
}

/// A closed peer window holds the writer, and the segment that reopens it wakes the writer, both
/// when it completes the handshake and when it arrives on an established connection.
#[tokio::test]
Expand Down
Loading