diff --git a/src/stream/tcb.rs b/src/stream/tcb.rs index abff5c1..7b2bc7a 100644 --- a/src/stream/tcb.rs +++ b/src/stream/tcb.rs @@ -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, @@ -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, @@ -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) { diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 212b7a0..ab6c62b 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -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, @@ -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 @@ -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]