fix(ipr): send KCP protocol packets in tick instead of just logging

- Add get_sender_tag() and fetch_outgoing_for_conv() to KcpSessionManager
- Change handle_kcp_tick() to actually send ACKs/retransmissions via mixnet
- Reduce KCP tick interval from 100ms to 10ms for better responsiveness

This fixes the KCP reliability protocol which was broken because
protocol packets (ACKs, retransmissions) were generated but never sent.
This commit is contained in:
durch
2025-12-23 15:23:20 +01:00
committed by Jędrzej Stuczyński
parent b340367106
commit c6180d8b76
2 changed files with 63 additions and 12 deletions
@@ -250,6 +250,37 @@ impl KcpSessionManager {
.unwrap_or(0)
}
/// Get the sender_tag associated with a session.
///
/// Returns None if the session doesn't exist or has no sender_tag.
pub fn get_sender_tag(&self, conv_id: u32) -> Option<AnonymousSenderTag> {
self.sessions.get(&conv_id)?.sender_tag
}
/// Fetch any pending outgoing KCP packets for a specific session.
///
/// This is used to send immediate ACKs after receiving packets,
/// rather than waiting for the periodic tick.
pub fn fetch_outgoing_for_conv(
&mut self,
conv_id: u32,
current_time_ms: u64,
) -> Option<Vec<u8>> {
let session = self.sessions.get_mut(&conv_id)?;
session.driver.update(current_time_ms);
let packets = session.driver.fetch_outgoing();
if packets.is_empty() {
return None;
}
let mut buf = BytesMut::new();
for pkt in packets {
pkt.encode(&mut buf);
}
Some(buf.to_vec())
}
/// Periodic update for all sessions.
///
/// This should be called periodically (e.g., every 10-100ms) to:
@@ -31,8 +31,8 @@ use std::{net::SocketAddr, time::Duration};
use tokio::io::AsyncWriteExt;
use tokio_util::codec::FramedRead;
/// KCP tick interval for session updates (retransmissions, cleanup)
const KCP_TICK_INTERVAL: Duration = Duration::from_millis(100);
/// KCP tick interval for session updates (retransmissions, ACKs, cleanup)
const KCP_TICK_INTERVAL: Duration = Duration::from_millis(10);
#[cfg(not(target_os = "linux"))]
type TunDevice = crate::non_linux_dummy::DummyDevice;
@@ -638,28 +638,48 @@ impl MixnetListener {
}
}
/// Handle KCP session tick - drives retransmissions and cleanup.
/// Handle KCP session tick - drives retransmissions, ACKs, and cleanup.
///
/// Returns any outgoing KCP packets that need to be sent (e.g., retransmissions).
/// Note: For LP clients, responses are sent via SURB, not directly here.
fn handle_kcp_tick(&mut self) {
/// Sends any pending outgoing KCP packets (ACKs, retransmissions) via the
/// sender_tag reply mechanism.
async fn handle_kcp_tick(&mut self) {
let current_time_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
// Tick all KCP sessions - this handles retransmissions internally
// Tick all KCP sessions - generates ACKs, retransmissions, etc.
let outgoing = self.kcp_session_manager.tick(current_time_ms);
// Log any pending outgoing data (would be sent via SURB in full implementation)
// Send any pending outgoing KCP protocol packets
for (conv_id, data) in outgoing {
// Get the sender_tag for this session to reply via mixnet
let Some(sender_tag) = self.kcp_session_manager.get_sender_tag(conv_id) else {
log::warn!(
"KCP tick: conv_id={} has {} bytes but no sender_tag, dropping",
conv_id,
data.len()
);
continue;
};
log::trace!(
"KCP tick: conv_id={} has {} bytes pending for SURB reply",
"KCP tick: conv_id={} sending {} bytes",
conv_id,
data.len()
);
// TODO: In full implementation, these would be sent via stored SURBs
// For now, we just log - the client will retransmit if needed
let reply_to = crate::clients::ConnectedClientId::AnonymousSenderTag(sender_tag);
let input_message =
crate::util::create_message::create_input_message(&reply_to, data);
if let Err(e) = self.mixnet_client.send(input_message).await {
log::warn!(
"KCP tick: failed to send for conv_id={}: {}",
conv_id,
e
);
}
}
}
@@ -678,7 +698,7 @@ impl MixnetListener {
self.handle_disconnect_timer().await;
},
_ = kcp_tick_timer.tick() => {
self.handle_kcp_tick();
self.handle_kcp_tick().await;
},
msg = self.mixnet_client.next() => {
if let Some(msg) = msg {