Skip to content
Closed
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
28 changes: 26 additions & 2 deletions tokio-quiche/src/quic/io/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,12 +617,15 @@ where
to: pkt.local_addr,
};

// A single UDP datagram may contain multiple coalesced QUIC packets
// (e.g. Initial + Handshake). `quiche::recv` only processes one packet
// per call, so loop until it signals Done to drain the whole datagram.
if let Some(gro) = pkt.gro {
for dgram in pkt.buf.chunks_mut(gro as usize) {
qconn.recv(dgram, recv_info)?;
recv_coalesced(qconn, dgram, recv_info)?;
}
} else {
qconn.recv(&mut pkt.buf, recv_info)?;
recv_coalesced(qconn, &mut pkt.buf, recv_info)?;
}

Ok(())
Expand Down Expand Up @@ -935,3 +938,24 @@ fn min_of_some<T: Ord>(v1: Option<T>, v2: Option<T>) -> Option<T> {
(None, None) => None,
}
}

/// Feed `buf` into `qconn.recv` repeatedly until quiche has consumed every
/// coalesced QUIC packet in the datagram. `quiche::Connection::recv` only
/// processes one packet per call; without this loop, any packets coalesced
/// after the first (e.g. a server's Initial+Handshake) are silently dropped.
fn recv_coalesced(
qconn: &mut QuicheConnection, buf: &mut [u8], recv_info: quiche::RecvInfo,
) -> QuicResult<()> {
let mut rest: &mut [u8] = buf;
while !rest.is_empty() {
match qconn.recv(rest, recv_info) {
Ok(n) => {
let (_, tail) = std::mem::take(&mut rest).split_at_mut(n);
rest = tail;
},
Err(quiche::Error::Done) => break,
Err(err) => return Err(err.into()),
}
}
Ok(())
}
35 changes: 31 additions & 4 deletions tokio-quiche/src/quic/router/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,15 @@ where
to: incoming.local_addr,
};

// A single UDP datagram may contain multiple coalesced QUIC packets
// (e.g. a server can coalesce Initial + Handshake). `quiche::recv`
// only processes one packet per call, so loop until it signals Done.
if let Some(gro) = incoming.gro {
for dgram in incoming.buf.chunks_mut(gro as usize) {
// Log error here if recv fails
let _ = conn.recv(dgram, recv_info);
recv_coalesced(&mut conn, dgram, recv_info);
}
} else {
// Log error here if recv fails
let _ = conn.recv(&mut incoming.buf, recv_info);
recv_coalesced(&mut conn, &mut incoming.buf, recv_info);
}

// disarm the timer since we're either going to immediately rearm it or
Expand Down Expand Up @@ -290,3 +291,29 @@ fn simple_conn_send<Tx: DatagramSocketSend + Send + Sync + 'static>(
}
}
}

/// Feed `buf` into `conn.recv` repeatedly until quiche has consumed every
/// coalesced QUIC packet in the datagram. `quiche::Connection::recv` only
/// processes one packet per call; without this loop, any packets coalesced
/// after the first (e.g. a server's Initial+Handshake) are silently dropped.
fn recv_coalesced(
conn: &mut QuicheConnection, buf: &mut [u8], recv_info: quiche::RecvInfo,
) {
let mut rest: &mut [u8] = buf;
while !rest.is_empty() {
match conn.recv(rest, recv_info) {
Ok(n) => {
let (_, tail) = std::mem::take(&mut rest).split_at_mut(n);
rest = tail;
},
Err(quiche::Error::Done) => break,
Err(err) => {
log::debug!(
"client connector recv error from {}: {err:?}",
recv_info.from
);
break;
},
}
}
}