diff --git a/service-providers/ip-packet-router/src/kcp_session_manager.rs b/service-providers/ip-packet-router/src/kcp_session_manager.rs index f94ce8452c..b326a3160b 100644 --- a/service-providers/ip-packet-router/src/kcp_session_manager.rs +++ b/service-providers/ip-packet-router/src/kcp_session_manager.rs @@ -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 { + 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> { + 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: diff --git a/service-providers/ip-packet-router/src/mixnet_listener.rs b/service-providers/ip-packet-router/src/mixnet_listener.rs index 1ee44b9da9..420faf9841 100644 --- a/service-providers/ip-packet-router/src/mixnet_listener.rs +++ b/service-providers/ip-packet-router/src/mixnet_listener.rs @@ -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 {