fix: resolve fatal connection halt caused by unrecoverable dropped untracked Ack/Nack frames. Control frames are now saved in sent_history without auto-retransmission to allow targeted Nack recovery.

This commit is contained in:
ospab 2026-05-17 02:40:52 +03:00
parent 9c05f130ac
commit 5c33f08a9b
2 changed files with 14 additions and 10 deletions

10
Cargo.lock generated
View File

@ -745,7 +745,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "ostp"
version = "0.1.56"
version = "0.1.57"
dependencies = [
"anyhow",
"base64",
@ -762,7 +762,7 @@ dependencies = [
[[package]]
name = "ostp-client"
version = "0.1.56"
version = "0.1.57"
dependencies = [
"anyhow",
"bytes",
@ -780,7 +780,7 @@ dependencies = [
[[package]]
name = "ostp-core"
version = "0.1.56"
version = "0.1.57"
dependencies = [
"anyhow",
"async-trait",
@ -813,7 +813,7 @@ dependencies = [
[[package]]
name = "ostp-server"
version = "0.1.56"
version = "0.1.57"
dependencies = [
"anyhow",
"bytes",
@ -828,7 +828,7 @@ dependencies = [
[[package]]
name = "ostp-tun-helper"
version = "0.1.56"
version = "0.1.57"
dependencies = [
"anyhow",
"chrono",

View File

@ -89,6 +89,7 @@ struct SentFrame {
bytes: Bytes,
last_sent: Instant,
retries: u8,
is_retransmittable: bool,
}
impl ProtocolMachine {
@ -356,7 +357,7 @@ impl ProtocolMachine {
self.build_datagram(stream_id, kind, payload, false)
}
fn build_datagram(&mut self, stream_id: u16, kind: FrameKind, payload: Bytes, track: bool) -> Result<Bytes, ProtocolError> {
fn build_datagram(&mut self, stream_id: u16, kind: FrameKind, payload: Bytes, is_retransmittable: bool) -> Result<Bytes, ProtocolError> {
let padding = self.padder.build_padding(payload.len());
let header = FrameHeader {
version: 1,
@ -395,9 +396,7 @@ impl ProtocolMachine {
let final_bytes = Bytes::from(out);
if track {
self.push_sent_frame(nonce, final_bytes.clone());
}
self.push_sent_frame(nonce, final_bytes.clone(), is_retransmittable);
Ok(final_bytes)
}
@ -417,6 +416,10 @@ impl ProtocolMachine {
let now = Instant::now();
let base_rto_ms = self.rto.as_millis().max(1) as u64;
for frame in self.sent_history.iter_mut() {
if !frame.is_retransmittable {
continue;
}
if frame.retries == self.max_retries {
tracing::warn!(
"Frame {} exceeded max retries ({}); continuing with backoff",
@ -518,12 +521,13 @@ impl ProtocolMachine {
None
}
fn push_sent_frame(&mut self, nonce: u64, bytes: Bytes) {
fn push_sent_frame(&mut self, nonce: u64, bytes: Bytes, is_retransmittable: bool) {
self.sent_history.push_back(SentFrame {
nonce,
bytes,
last_sent: Instant::now(),
retries: 0,
is_retransmittable,
});
while self.sent_history.len() > self.max_sent_history {
self.sent_history.pop_front();