Loading debian/changelog +14 −0 Original line number Diff line number Diff line libnetplus (20260504+5) unstable; urgency=medium * QUIC: fix critical data-loss bug in loss-detection retransmit — if sendPacket() failed (EAGAIN / send-buffer full after a burst), the stream data was erased from _sent_packets but never re-inserted, causing permanent loss of ~47 in-flight packets. Fix: on send failure, re-queue under the original PN so the next loss-detection cycle retries the retransmit. * QUIC: fix same data-loss bug in PTO retransmit path — only erase the original entry and increment _pto_count after sendPacket() succeeds; re-queue on failure. * QUIC: improve [QUIC-DIAG] retransmit logging — report retransmit_ok / retransmit_fail counts and PTO re-queue events libnetplus (20260504+4) unstable; urgency=medium * QUIC: add client-side [QUIC-DIAG] logging for ACK processing (first Loading src/quic.cpp +28 −17 Original line number Diff line number Diff line Loading @@ -3661,8 +3661,8 @@ void quic::checkLossAndRetransmit() { if (age >= pto_thresh) { // Retransmit the oldest unacked packet as a probe auto sp = oldest; // copy — will be erased below uint64_t old_pn = _sent_packets.begin()->first; _sent_packets.erase(_sent_packets.begin()); ++_pto_count; if (sp.data_length > 0 || sp.fin) { uint8_t frame_type = 0x08 | 0x02; Loading @@ -3689,14 +3689,16 @@ void quic::checkLossAndRetransmit() { recordSentPacket(new_pn, sp.stream_id, sp.stream_offset, sp.stream_data.data(), sp.stream_data.size(), sp.fin, packet.size()); ++_pto_count; } else { // Send failed (EAGAIN) — re-queue under original PN // so the data is not permanently lost _sent_packets[old_pn] = std::move(sp); std::cerr << "[QUIC-DIAG] PTO send failed, re-queued pn=" << old_pn << "\n"; } QUIC_DBG("PTO probe: new_pn=%lu age=%.3fs pto=%.3fs", (unsigned long)new_pn, age, pto_thresh); std::cerr << "[QUIC-DIAG] PTO probe: age=" << age << "s pto=" << pto_thresh << "s sent_packets=" << _sent_packets.size() << " largest_acked=" << _largest_acked_pn << "\n"; } } } Loading Loading @@ -3744,12 +3746,7 @@ void quic::checkLossAndRetransmit() { } // Retransmit lost stream data with new packet numbers if (!lost.empty()) { std::cerr << "[QUIC-DIAG] loss detected: " << lost.size() << " packets, largest_acked=" << _largest_acked_pn << " sent_packets_remaining=" << _sent_packets.size() << "\n"; } size_t retransmit_ok = 0, retransmit_fail = 0; for (auto& sp : lost) { if (sp.data_length == 0 && !sp.fin) continue; Loading Loading @@ -3784,9 +3781,6 @@ void quic::checkLossAndRetransmit() { ssize_t sent = sendPacket(packet.data(), packet.size()); if (sent < 0) { // Single non-blocking retry — do not sleep with quic_mtx held. // If the socket is still busy the packet will be retransmitted // on the next loss-detection cycle. sent = sendPacket(packet.data(), packet.size()); } Loading @@ -3794,12 +3788,29 @@ void quic::checkLossAndRetransmit() { // Track retransmitted packet under new PN recordSentPacket(new_pn, sp.stream_id, sp.stream_offset, sp.stream_data.data(), sp.stream_data.size(), sp.fin, packet.size()); ++retransmit_ok; } else { // Send failed (EAGAIN / send-buffer full) — re-insert under // the original PN so the packet is retried on the next // loss-detection cycle instead of being permanently lost. size_t saved_size = sp.sent_size; _sent_packets[sp.pn] = std::move(sp); _bytes_in_flight += saved_size; ++retransmit_fail; } QUIC_DBG("RETRANSMIT: old_pn=%lu new_pn=%lu sid=%lu off=%lu len=%zu fin=%d", QUIC_DBG("RETRANSMIT: old_pn=%lu new_pn=%lu sid=%lu off=%lu len=%zu fin=%d sent=%zd", (unsigned long)sp.pn, (unsigned long)new_pn, (unsigned long)sp.stream_id, (unsigned long)sp.stream_offset, sp.data_length, (int)sp.fin); sp.data_length, (int)sp.fin, sent); } if (!lost.empty()) { std::cerr << "[QUIC-DIAG] loss detected: " << lost.size() << " packets, largest_acked=" << _largest_acked_pn << " retransmit_ok=" << retransmit_ok << " retransmit_fail=" << retransmit_fail << " sent_packets=" << _sent_packets.size() << "\n"; } } Loading Loading
debian/changelog +14 −0 Original line number Diff line number Diff line libnetplus (20260504+5) unstable; urgency=medium * QUIC: fix critical data-loss bug in loss-detection retransmit — if sendPacket() failed (EAGAIN / send-buffer full after a burst), the stream data was erased from _sent_packets but never re-inserted, causing permanent loss of ~47 in-flight packets. Fix: on send failure, re-queue under the original PN so the next loss-detection cycle retries the retransmit. * QUIC: fix same data-loss bug in PTO retransmit path — only erase the original entry and increment _pto_count after sendPacket() succeeds; re-queue on failure. * QUIC: improve [QUIC-DIAG] retransmit logging — report retransmit_ok / retransmit_fail counts and PTO re-queue events libnetplus (20260504+4) unstable; urgency=medium * QUIC: add client-side [QUIC-DIAG] logging for ACK processing (first Loading
src/quic.cpp +28 −17 Original line number Diff line number Diff line Loading @@ -3661,8 +3661,8 @@ void quic::checkLossAndRetransmit() { if (age >= pto_thresh) { // Retransmit the oldest unacked packet as a probe auto sp = oldest; // copy — will be erased below uint64_t old_pn = _sent_packets.begin()->first; _sent_packets.erase(_sent_packets.begin()); ++_pto_count; if (sp.data_length > 0 || sp.fin) { uint8_t frame_type = 0x08 | 0x02; Loading @@ -3689,14 +3689,16 @@ void quic::checkLossAndRetransmit() { recordSentPacket(new_pn, sp.stream_id, sp.stream_offset, sp.stream_data.data(), sp.stream_data.size(), sp.fin, packet.size()); ++_pto_count; } else { // Send failed (EAGAIN) — re-queue under original PN // so the data is not permanently lost _sent_packets[old_pn] = std::move(sp); std::cerr << "[QUIC-DIAG] PTO send failed, re-queued pn=" << old_pn << "\n"; } QUIC_DBG("PTO probe: new_pn=%lu age=%.3fs pto=%.3fs", (unsigned long)new_pn, age, pto_thresh); std::cerr << "[QUIC-DIAG] PTO probe: age=" << age << "s pto=" << pto_thresh << "s sent_packets=" << _sent_packets.size() << " largest_acked=" << _largest_acked_pn << "\n"; } } } Loading Loading @@ -3744,12 +3746,7 @@ void quic::checkLossAndRetransmit() { } // Retransmit lost stream data with new packet numbers if (!lost.empty()) { std::cerr << "[QUIC-DIAG] loss detected: " << lost.size() << " packets, largest_acked=" << _largest_acked_pn << " sent_packets_remaining=" << _sent_packets.size() << "\n"; } size_t retransmit_ok = 0, retransmit_fail = 0; for (auto& sp : lost) { if (sp.data_length == 0 && !sp.fin) continue; Loading Loading @@ -3784,9 +3781,6 @@ void quic::checkLossAndRetransmit() { ssize_t sent = sendPacket(packet.data(), packet.size()); if (sent < 0) { // Single non-blocking retry — do not sleep with quic_mtx held. // If the socket is still busy the packet will be retransmitted // on the next loss-detection cycle. sent = sendPacket(packet.data(), packet.size()); } Loading @@ -3794,12 +3788,29 @@ void quic::checkLossAndRetransmit() { // Track retransmitted packet under new PN recordSentPacket(new_pn, sp.stream_id, sp.stream_offset, sp.stream_data.data(), sp.stream_data.size(), sp.fin, packet.size()); ++retransmit_ok; } else { // Send failed (EAGAIN / send-buffer full) — re-insert under // the original PN so the packet is retried on the next // loss-detection cycle instead of being permanently lost. size_t saved_size = sp.sent_size; _sent_packets[sp.pn] = std::move(sp); _bytes_in_flight += saved_size; ++retransmit_fail; } QUIC_DBG("RETRANSMIT: old_pn=%lu new_pn=%lu sid=%lu off=%lu len=%zu fin=%d", QUIC_DBG("RETRANSMIT: old_pn=%lu new_pn=%lu sid=%lu off=%lu len=%zu fin=%d sent=%zd", (unsigned long)sp.pn, (unsigned long)new_pn, (unsigned long)sp.stream_id, (unsigned long)sp.stream_offset, sp.data_length, (int)sp.fin); sp.data_length, (int)sp.fin, sent); } if (!lost.empty()) { std::cerr << "[QUIC-DIAG] loss detected: " << lost.size() << " packets, largest_acked=" << _largest_acked_pn << " retransmit_ok=" << retransmit_ok << " retransmit_fail=" << retransmit_fail << " sent_packets=" << _sent_packets.size() << "\n"; } } Loading