1use crate::control::queries;
4use crate::control::{ControlMessage, ControlSenders, ControlSocket, commands};
5use crate::discovery::is_punch_packet;
6use crate::node::decrypt_worker::{
7 DecryptFailureReport, DecryptFallback, DecryptJobBatcher, DecryptWorkerEvent,
8 DecryptWorkerFallbackReceivers,
9};
10use crate::node::handlers::encrypted::EncryptedFrameFastPath;
11use crate::node::wire::{
12 COMMON_PREFIX_SIZE, CommonPrefix, FMP_VERSION, PHASE_ESTABLISHED, PHASE_MSG1, PHASE_MSG2,
13};
14use crate::node::{
15 AuthenticatedFmpPlaintext, EndpointSendBatchCommand, Node, NodeEndpointCommand, NodeError,
16};
17use crate::transport::PacketRx;
18use crate::transport::ReceivedPacket;
19use crate::upper::tun::TunOutboundRx;
20use std::time::{Duration, Instant};
21use tokio::sync::mpsc::Receiver;
22use tracing::{debug, info, trace, warn};
23
24mod budget;
25mod drain;
26
27#[cfg(test)]
28mod tests;
29
30use budget::*;
31use drain::*;
32
33impl Node {
34 pub async fn run_rx_loop(&mut self) -> Result<(), NodeError> {
54 let mut packet_rx = self.packet_rx.take().ok_or(NodeError::NotStarted)?;
55
56 let (mut tun_outbound_rx, _tun_guard) = match self.tun_outbound_rx.take() {
60 Some(rx) => (rx, None),
61 None => {
62 let (tx, rx) = tokio::sync::mpsc::channel(1);
63 (rx, Some(tx))
64 }
65 };
66
67 let (mut dns_identity_rx, _dns_guard) = match self.dns_identity_rx.take() {
70 Some(rx) => (rx, None),
71 None => {
72 let (tx, rx) = tokio::sync::mpsc::channel(1);
73 (rx, Some(tx))
74 }
75 };
76
77 let (mut endpoint_priority_command_rx, _endpoint_priority_command_guard) =
80 match self.endpoint_priority_command_rx.take() {
81 Some(rx) => (rx, None),
82 None => {
83 let (tx, rx) = tokio::sync::mpsc::channel(1);
84 (rx, Some(tx))
85 }
86 };
87 let (mut endpoint_command_rx, _endpoint_command_guard) =
88 match self.endpoint_command_rx.take() {
89 Some(rx) => (rx, None),
90 None => {
91 let (tx, rx) = tokio::sync::mpsc::channel(1);
92 (rx, Some(tx))
93 }
94 };
95 let (mut endpoint_bulk_feedback_rx, _endpoint_bulk_feedback_guard) =
96 match self.endpoint_bulk_feedback_rx.take() {
97 Some(rx) => (rx, None),
98 None => {
99 let (tx, rx) = tokio::sync::mpsc::channel(1);
100 (rx, Some(tx))
101 }
102 };
103
104 let (mut decrypt_fallback_rx, _decrypt_fallback_guard) =
108 match self.decrypt_fallback_rx.take() {
109 Some(rx) => (rx, None),
110 None => {
111 let (tx, rx) = crate::node::decrypt_worker::decrypt_worker_fallback_channels();
112 (rx, Some(tx))
113 }
114 };
115
116 let mut tick =
117 tokio::time::interval(Duration::from_secs(self.config.node.tick_interval_secs));
118 tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
119 let mut maintenance_state = RxLoopMaintenanceState::default();
120
121 let (control_query_tx, mut control_query_rx) =
125 tokio::sync::mpsc::channel::<ControlMessage>(32);
126 let (control_command_tx, mut control_command_rx) =
127 tokio::sync::mpsc::channel::<ControlMessage>(32);
128
129 if self.config.node.control.enabled {
130 let config = self.config.node.control.clone();
131 let senders = ControlSenders::new(control_query_tx.clone(), control_command_tx.clone());
132 tokio::spawn(async move {
133 match ControlSocket::bind(&config) {
134 Ok(socket) => {
135 socket.accept_loop_split(senders).await;
136 }
137 Err(e) => {
138 warn!(error = %e, "Failed to bind control socket");
139 }
140 }
141 });
142 }
143 drop(control_query_tx);
145 drop(control_command_tx);
146
147 info!("RX event loop started");
148 crate::perf_profile::maybe_spawn_reporter();
150 tick.tick().await;
154
155 loop {
156 tokio::select! {
157 biased;
158 Some(event) = decrypt_fallback_rx.priority.recv() => {
169 let fallback_drained = self.drain_decrypt_priority_fallback(
170 &mut decrypt_fallback_rx.priority,
171 Some(event),
172 PACKET_DRAIN_BUDGET,
173 ).await;
174 let side_drained = self.drain_rx_loop_side_queues(
175 &mut control_query_rx,
176 &mut endpoint_bulk_feedback_rx,
177 &mut tun_outbound_rx,
178 &mut endpoint_priority_command_rx,
179 &mut endpoint_command_rx,
180 SIDE_QUEUE_INTERLEAVE_BUDGET,
181 ).await;
182 if fallback_drained > 0 || side_drained.has_data_drained() {
183 maintenance_state.record_data_activity(Instant::now());
184 }
185 }
186 _ = tick.tick() => {
192 let drained = self.drain_rx_loop_data_queues(
193 &mut packet_rx,
194 &mut decrypt_fallback_rx,
195 &mut endpoint_bulk_feedback_rx,
196 &mut tun_outbound_rx,
197 &mut endpoint_priority_command_rx,
198 &mut endpoint_command_rx,
199 NON_PACKET_DRAIN_BUDGET,
200 ).await;
201 if drained.has_drained() {
202 maintenance_state.record_data_activity(Instant::now());
203 debug!(
204 drained = drained.total(),
205 drained_packets = drained.packets,
206 drained_decrypt = drained.decrypt,
207 drained_tun = drained.tun,
208 drained_endpoint = drained.endpoint,
209 "Drained queued packets before rx-loop maintenance"
210 );
211 }
212 let maintenance_plan = maintenance_state.plan_maintenance(
213 drained,
214 Instant::now(),
215 RX_LOOP_RECENT_DATA_ACTIVITY_WINDOW,
216 RX_LOOP_SLOW_MAINTENANCE_IDLE_TIMEOUT,
217 RX_LOOP_SLOW_MAINTENANCE_BUSY_TIMEOUT,
218 );
219
220 let slow_timed_out = self.run_rx_loop_maintenance_tick(
221 maintenance_plan,
222 ).await;
223 maintenance_state.record_maintenance_result(
224 maintenance_plan.data_pressure(),
225 slow_timed_out,
226 );
227
228 let post_drained = self.drain_rx_loop_data_queues(
229 &mut packet_rx,
230 &mut decrypt_fallback_rx,
231 &mut endpoint_bulk_feedback_rx,
232 &mut tun_outbound_rx,
233 &mut endpoint_priority_command_rx,
234 &mut endpoint_command_rx,
235 PACKET_DRAIN_BUDGET,
236 ).await;
237 if post_drained.has_drained() {
238 maintenance_state.record_data_activity(Instant::now());
239 debug!(
240 drained = post_drained.total(),
241 drained_packets = post_drained.packets,
242 drained_decrypt = post_drained.decrypt,
243 drained_tun = post_drained.tun,
244 drained_endpoint = post_drained.endpoint,
245 "Drained queued packets after rx-loop maintenance"
246 );
247 }
248 }
249 Some(event) = decrypt_fallback_rx.authenticated_bulk.recv(),
250 if authenticated_bulk_preempts_packet_rx(packet_rx.priority_ready_packets()) =>
251 {
252 let fallback_drained = self.drain_decrypt_fallback(
253 &mut decrypt_fallback_rx,
254 None,
255 Some(event),
256 None,
257 NON_PACKET_DRAIN_BUDGET,
258 ).await;
259 let side_drained = self.drain_rx_loop_side_queues(
260 &mut control_query_rx,
261 &mut endpoint_bulk_feedback_rx,
262 &mut tun_outbound_rx,
263 &mut endpoint_priority_command_rx,
264 &mut endpoint_command_rx,
265 SIDE_QUEUE_INTERLEAVE_BUDGET,
266 ).await;
267 if fallback_drained > 0 || side_drained.has_data_drained() {
268 maintenance_state.record_data_activity(Instant::now());
269 }
270 }
271 Some(message) = control_query_rx.recv() => {
272 self.drain_control_queries(
273 &mut control_query_rx,
274 Some(message),
275 NON_PACKET_DRAIN_BUDGET,
276 ).await;
277 }
278 Some(feedback) = endpoint_bulk_feedback_rx.recv() => {
279 let drained = self.drain_endpoint_bulk_send_feedback(
280 &mut endpoint_bulk_feedback_rx,
281 Some(feedback),
282 NON_PACKET_DRAIN_BUDGET,
283 );
284 if drained > 0 {
285 maintenance_state.record_data_activity(Instant::now());
286 }
287 }
288 Some(command) = endpoint_priority_command_rx.recv() => {
295 let drained = self.drain_endpoint_commands(
296 &mut endpoint_priority_command_rx,
297 &mut endpoint_command_rx,
298 Some(command),
299 None,
300 NON_PACKET_DRAIN_BUDGET,
301 ).await;
302 if drained > 0 {
303 maintenance_state.record_data_activity(Instant::now());
304 }
305 }
306 packet = packet_rx.recv() => {
307 match packet {
308 Some(p) => {
309 let drained = self.drain_packet_rx(
310 &mut packet_rx,
311 &mut decrypt_fallback_rx,
312 Some(RxLoopSideQueues {
313 control_query_rx: &mut control_query_rx,
314 endpoint_bulk_feedback_rx: &mut endpoint_bulk_feedback_rx,
315 tun_outbound_rx: &mut tun_outbound_rx,
316 endpoint_priority_command_rx: &mut endpoint_priority_command_rx,
317 endpoint_command_rx: &mut endpoint_command_rx,
318 }),
319 Some(p),
320 PACKET_DRAIN_BUDGET,
321 ).await;
322 if drained > 0 {
323 maintenance_state.record_data_activity(Instant::now());
324 }
325 }
326 None => break, }
328 }
329 Some(event) = decrypt_fallback_rx.bulk.recv() => {
330 let fallback_plan = fallback_drain_plan();
331 let fallback_drained = self.drain_decrypt_fallback(
332 &mut decrypt_fallback_rx,
333 None,
334 None,
335 Some(event),
336 fallback_plan.trailing_budget,
337 ).await;
338 let side_drained = self.drain_rx_loop_side_queues(
339 &mut control_query_rx,
340 &mut endpoint_bulk_feedback_rx,
341 &mut tun_outbound_rx,
342 &mut endpoint_priority_command_rx,
343 &mut endpoint_command_rx,
344 SIDE_QUEUE_INTERLEAVE_BUDGET,
345 ).await;
346 if fallback_drained > 0 || side_drained.has_data_drained() {
347 maintenance_state.record_data_activity(Instant::now());
348 }
349 }
350 Some(ipv6_packet) = tun_outbound_rx.recv() => {
351 let drained = self.drain_tun_outbound(
352 &mut tun_outbound_rx,
353 Some(ipv6_packet),
354 NON_PACKET_DRAIN_BUDGET,
355 ).await;
356 if drained > 0 {
357 maintenance_state.record_data_activity(Instant::now());
358 }
359 }
360 Some(identity) = dns_identity_rx.recv() => {
361 debug!(
362 node_addr = %identity.node_addr,
363 "Registering identity from DNS resolution"
364 );
365 self.register_identity(identity.node_addr, identity.pubkey);
366 }
367 Some(command) = endpoint_command_rx.recv() => {
368 let drained = self.drain_endpoint_commands(
369 &mut endpoint_priority_command_rx,
370 &mut endpoint_command_rx,
371 None,
372 Some(command),
373 NON_PACKET_DRAIN_BUDGET,
374 ).await;
375 if drained > 0 {
376 maintenance_state.record_data_activity(Instant::now());
377 }
378 }
379 Some((request, response_tx)) = control_command_rx.recv() => {
380 let response = commands::dispatch(
381 self,
382 &request.command,
383 request.params.as_ref(),
384 ).await;
385 let _ = response_tx.send(response);
386 }
387 }
388 }
389
390 info!("RX event loop stopped (channel closed)");
391 Ok(())
392 }
393
394 async fn drain_rx_loop_data_queues(
395 &mut self,
396 packet_rx: &mut PacketRx,
397 decrypt_fallback_rx: &mut DecryptWorkerFallbackReceivers,
398 endpoint_bulk_feedback_rx: &mut Receiver<crate::node::EndpointBulkSendFeedback>,
399 tun_outbound_rx: &mut TunOutboundRx,
400 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
401 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
402 budget: usize,
403 ) -> RxLoopDataDrainStats {
404 let drained_packets = self
405 .drain_packet_rx(packet_rx, decrypt_fallback_rx, None, None, budget)
406 .await;
407 let non_packet_budget = non_packet_drain_budget(budget);
408 let drained_decrypt = if decrypt_fallback_has_ready(decrypt_fallback_rx) {
409 self.drain_decrypt_fallback(decrypt_fallback_rx, None, None, None, non_packet_budget)
410 .await
411 } else {
412 0
413 };
414 let drained_endpoint_feedback = self.drain_endpoint_bulk_send_feedback(
415 endpoint_bulk_feedback_rx,
416 None,
417 non_packet_budget,
418 );
419 let drained_tun = self
420 .drain_tun_outbound(tun_outbound_rx, None, non_packet_budget)
421 .await;
422 let drained_endpoint = self
423 .drain_endpoint_commands(
424 endpoint_priority_command_rx,
425 endpoint_command_rx,
426 None,
427 None,
428 non_packet_budget,
429 )
430 .await;
431 RxLoopDataDrainStats::with_feedback(
432 drained_packets,
433 drained_decrypt,
434 drained_endpoint_feedback,
435 drained_tun,
436 drained_endpoint,
437 )
438 }
439
440 async fn drain_packet_rx(
441 &mut self,
442 packet_rx: &mut PacketRx,
443 decrypt_fallback_rx: &mut DecryptWorkerFallbackReceivers,
444 mut side_queues: Option<RxLoopSideQueues<'_>>,
445 first_packet: Option<ReceivedPacket>,
446 budget: usize,
447 ) -> usize {
448 self.begin_endpoint_event_batch();
454 let side_queue_interleave_every = if side_queues.is_some() {
455 SIDE_QUEUE_INTERLEAVE_EVERY
456 } else {
457 0
458 };
459 let fallback_plan = fallback_drain_plan();
460 let mut drain = PacketDrainCursor::new(
461 first_packet,
462 budget,
463 fallback_plan.interleave_every,
464 side_queue_interleave_every,
465 );
466 let mut decrypt_jobs = DecryptJobBatcher::new();
467 while let Some(action) = drain.next(packet_rx) {
468 match action {
469 PacketDrainAction::Packet(packet) => {
470 let action = self.begin_process_packet(packet);
471 match action {
472 PacketProcessAction::DecryptJob { job } => {
473 if let Some(workers) = self.decrypt_workers.as_ref() {
474 decrypt_jobs.push(workers, job);
475 }
476 }
477 PacketProcessAction::Done => {}
478 action => {
479 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
480 self.finish_packet_process(action).await;
481 }
482 }
483 }
484 PacketDrainAction::InterleaveFallback => {
485 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
486 let drained = if decrypt_fallback_has_ready(decrypt_fallback_rx) {
487 self.drain_decrypt_fallback(
488 decrypt_fallback_rx,
489 None,
490 None,
491 None,
492 fallback_plan.interleave_budget,
493 )
494 .await
495 } else {
496 0
497 };
498 if drained == 0 {
499 drain.refund_empty_interleave_turn();
500 }
501 }
502 PacketDrainAction::InterleaveSideQueues => {
503 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
504 let drained = if let Some(side_queues) = side_queues.as_mut() {
505 if rx_loop_side_queues_have_ready(side_queues) {
506 self.drain_rx_loop_side_queues(
507 side_queues.control_query_rx,
508 side_queues.endpoint_bulk_feedback_rx,
509 side_queues.tun_outbound_rx,
510 side_queues.endpoint_priority_command_rx,
511 side_queues.endpoint_command_rx,
512 SIDE_QUEUE_INTERLEAVE_BUDGET,
513 )
514 .await
515 } else {
516 RxLoopDataDrainStats::default()
517 }
518 } else {
519 RxLoopDataDrainStats::default()
520 };
521 if !drained.has_drained() {
522 drain.refund_empty_interleave_turn();
523 }
524 }
525 }
526 }
527
528 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
529 let drained = drain.drained();
530 if drained > 0 {
531 self.drain_decrypt_fallback(
536 decrypt_fallback_rx,
537 None,
538 None,
539 None,
540 fallback_plan.trailing_budget.min(budget),
541 )
542 .await;
543 self.finish_endpoint_event_batch();
544 } else {
545 self.finish_endpoint_event_batch();
546 }
547 drained
548 }
549
550 async fn drain_rx_loop_side_queues(
551 &mut self,
552 control_query_rx: &mut Receiver<ControlMessage>,
553 endpoint_bulk_feedback_rx: &mut Receiver<crate::node::EndpointBulkSendFeedback>,
554 tun_outbound_rx: &mut TunOutboundRx,
555 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
556 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
557 budget: usize,
558 ) -> RxLoopDataDrainStats {
559 let drained_endpoint_feedback =
560 self.drain_endpoint_bulk_send_feedback(endpoint_bulk_feedback_rx, None, budget);
561 let feedback_remaining_budget = budget.saturating_sub(drained_endpoint_feedback);
562 let control_budget = feedback_remaining_budget.min(CONTROL_QUERY_INTERLEAVE_BUDGET);
563 let drained_control = self
564 .drain_control_queries(control_query_rx, None, control_budget)
565 .await;
566 let remaining_budget = feedback_remaining_budget.saturating_sub(drained_control);
567 let (endpoint_budget, tun_budget) = split_side_queue_budget(remaining_budget);
568 let mut drained_endpoint = self
569 .drain_endpoint_commands(
570 endpoint_priority_command_rx,
571 endpoint_command_rx,
572 None,
573 None,
574 endpoint_budget,
575 )
576 .await;
577 let mut drained_tun = self
578 .drain_tun_outbound(tun_outbound_rx, None, tun_budget)
579 .await;
580
581 let endpoint_remainder = remaining_side_queue_budget(endpoint_budget, drained_endpoint);
582 let tun_remainder = remaining_side_queue_budget(tun_budget, drained_tun);
583 if endpoint_remainder > 0 && !tun_outbound_rx.is_empty() {
584 drained_tun += self
585 .drain_tun_outbound(tun_outbound_rx, None, endpoint_remainder)
586 .await;
587 }
588 if tun_remainder > 0
589 && (!endpoint_priority_command_rx.is_empty() || !endpoint_command_rx.is_empty())
590 {
591 drained_endpoint += self
592 .drain_endpoint_commands(
593 endpoint_priority_command_rx,
594 endpoint_command_rx,
595 None,
596 None,
597 tun_remainder,
598 )
599 .await;
600 }
601
602 RxLoopDataDrainStats::with_control(
603 0,
604 drained_endpoint_feedback,
605 drained_tun,
606 drained_endpoint,
607 drained_control,
608 )
609 }
610
611 async fn drain_control_queries(
612 &mut self,
613 control_query_rx: &mut Receiver<ControlMessage>,
614 first_message: Option<ControlMessage>,
615 budget: usize,
616 ) -> usize {
617 let mut drain = SingleLaneDrainCursor::new(first_message, budget);
618 while let Some((request, response_tx)) = drain.next(control_query_rx) {
619 let response = queries::dispatch(self, &request.command, request.params.as_ref());
620 let _ = response_tx.send(response);
621 }
622
623 drain.drained()
624 }
625
626 async fn drain_tun_outbound(
627 &mut self,
628 tun_outbound_rx: &mut TunOutboundRx,
629 first_packet: Option<Vec<u8>>,
630 budget: usize,
631 ) -> usize {
632 let mut drain = SingleLaneDrainCursor::new(first_packet, budget);
633 while let Some(packet) = drain.next(tun_outbound_rx) {
634 self.handle_tun_outbound(packet).await;
635 }
636
637 drain.drained()
638 }
639
640 async fn drain_endpoint_commands(
641 &mut self,
642 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
643 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
644 first_priority_command: Option<NodeEndpointCommand>,
645 first_bulk_command: Option<NodeEndpointCommand>,
646 budget: usize,
647 ) -> usize {
648 let mut drain =
649 PriorityBulkDrainCursor::new(first_priority_command, first_bulk_command, budget);
650 while let Some(command) = drain.next(endpoint_priority_command_rx, endpoint_command_rx) {
651 let drain_cost = command.drain_cost();
652 match command.into_send_batch_oneway() {
653 Ok((batch, _lane)) => {
654 let mut batch_commands = vec![batch];
655 self.coalesce_endpoint_send_batch_commands(
656 &mut drain,
657 endpoint_priority_command_rx,
658 endpoint_command_rx,
659 &mut batch_commands,
660 );
661 self.handle_endpoint_send_batch_commands(batch_commands)
662 .await;
663 }
664 Err(command) => {
665 self.handle_endpoint_data_command(command).await;
666 }
667 }
668 drain.charge_extra(drain_cost.saturating_sub(1));
669 }
670
671 drain.drained()
672 }
673
674 fn drain_endpoint_bulk_send_feedback(
675 &mut self,
676 endpoint_bulk_feedback_rx: &mut Receiver<crate::node::EndpointBulkSendFeedback>,
677 first_feedback: Option<crate::node::EndpointBulkSendFeedback>,
678 budget: usize,
679 ) -> usize {
680 let mut drain = SingleLaneDrainCursor::new(first_feedback, budget);
681 while let Some(feedback) = drain.next(endpoint_bulk_feedback_rx) {
682 self.apply_endpoint_bulk_send_feedback(feedback);
683 }
684
685 drain.drained()
686 }
687
688 fn coalesce_endpoint_send_batch_commands(
689 &mut self,
690 drain: &mut PriorityBulkDrainCursor<NodeEndpointCommand>,
691 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
692 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
693 batch_commands: &mut Vec<EndpointSendBatchCommand>,
694 ) {
695 let mut payloads = batch_commands
696 .iter()
697 .fold(0usize, |total, command| total.saturating_add(command.len()));
698 while payloads < ENDPOINT_COMMAND_COALESCE_MAX_PACKETS {
699 let Some(command) =
700 drain.next_bulk_if_no_priority(endpoint_priority_command_rx, endpoint_command_rx)
701 else {
702 break;
703 };
704 let drain_cost = command.drain_cost();
705 match command.into_send_batch_oneway() {
706 Ok((batch, _lane))
707 if batch_commands.last().is_some_and(|last| {
708 last.can_coalesce_with(&batch, ENDPOINT_COMMAND_COALESCE_MAX_PACKETS)
709 }) =>
710 {
711 payloads = payloads.saturating_add(batch.len());
712 batch_commands.push(batch);
713 drain.charge_extra(drain_cost.saturating_sub(1));
714 }
715 Ok((batch, lane)) => {
716 drain.defer_bulk(NodeEndpointCommand::SendBatchOneway {
717 command: batch,
718 lane,
719 });
720 break;
721 }
722 Err(command) => {
723 drain.defer_bulk(command);
724 break;
725 }
726 }
727 }
728 }
729
730 async fn run_rx_loop_maintenance_tick(&mut self, plan: RxLoopMaintenancePlan) -> bool {
731 self.check_timeouts();
732 let now_ms = Self::now_ms();
733 self.check_link_heartbeats().await;
737 self.reload_peer_acl();
738 self.resend_pending_handshakes(now_ms).await;
739 self.resend_pending_rekeys(now_ms).await;
740 self.resend_pending_session_handshakes(now_ms).await;
741 self.resend_pending_session_msg3(now_ms).await;
742 self.purge_idle_sessions(now_ms);
743 self.purge_learned_routes(now_ms);
744 self.check_mmp_reports().await;
745 self.check_session_mmp_reports().await;
746 self.check_rekey().await;
747 self.check_session_rekey().await;
748 self.check_pending_lookups(now_ms).await;
749 self.poll_pending_connects().await;
750 self.process_pending_retries(now_ms).await;
751 self.poll_transport_discovery().await;
752 self.activate_connected_udp_sessions().await;
753 self.sample_transport_congestion();
754
755 let Some(slow_timeout) = plan.slow_timeout() else {
756 crate::perf_profile::record_event(
757 crate::perf_profile::Event::RxLoopSlowMaintenanceSkipped,
758 );
759 return false;
760 };
761
762 if tokio::time::timeout(slow_timeout, self.run_rx_loop_slow_maintenance_tick())
763 .await
764 .is_err()
765 {
766 crate::perf_profile::record_event(
767 crate::perf_profile::Event::RxLoopSlowMaintenanceTimeout,
768 );
769 self.mark_rx_loop_maintenance_timeout();
770 warn!(
771 timeout_ms = slow_timeout.as_millis() as u64,
772 data_pressure = plan.data_pressure(),
773 "RX loop slow maintenance timed out; continuing packet processing"
774 );
775 return true;
776 }
777 false
778 }
779
780 async fn run_rx_loop_slow_maintenance_tick(&mut self) {
781 if let Some(delay) = rx_loop_slow_maintenance_fault_delay() {
782 tokio::time::sleep(delay).await;
783 }
784
785 self.poll_nostr_discovery().await;
790 self.poll_lan_discovery().await;
791 self.poll_local_instance_discovery().await;
792 self.check_tree_state().await;
793 self.check_bloom_state().await;
794 self.compute_mesh_size();
795 self.record_stats_history();
796 }
797
798 async fn process_decrypt_worker_event(&mut self, event: DecryptWorkerEvent) {
803 event.record_queue_wait();
804 match event {
805 DecryptWorkerEvent::Plaintext(fallback) => {
806 self.process_decrypt_fallback(fallback).await;
807 }
808 DecryptWorkerEvent::PlaintextBatch(fallbacks) => {
809 for fallback in fallbacks {
810 self.process_decrypt_fallback(fallback).await;
811 }
812 }
813 DecryptWorkerEvent::AuthenticatedFmpReceive(receive) => {
814 self.process_authenticated_fmp_receive_from_worker(receive);
815 }
816 DecryptWorkerEvent::AuthenticatedSession(session) => {
817 self.process_authenticated_session_from_worker(session)
818 .await;
819 }
820 DecryptWorkerEvent::AuthenticatedSessionBatch(sessions) => {
821 self.process_authenticated_session_batch_from_worker(sessions)
822 .await;
823 }
824 DecryptWorkerEvent::DirectSessionCommit(commit) => {
825 self.process_direct_session_commit_from_worker(commit).await;
826 }
827 DecryptWorkerEvent::DirectSessionCommitBatch(commits) => {
828 self.process_direct_session_commit_batch_from_worker(commits)
829 .await;
830 }
831 DecryptWorkerEvent::DirectSessionData(direct) => {
832 self.process_direct_session_data_from_worker(direct).await;
833 }
834 DecryptWorkerEvent::DirectSessionDataBatch(directs) => {
835 self.process_direct_session_data_batch_from_worker(directs)
836 .await;
837 }
838 DecryptWorkerEvent::FspDecryptFailure(report) => {
839 self.process_fsp_decrypt_failure_from_worker(report).await;
840 }
841 DecryptWorkerEvent::DecryptFailure(report) => {
842 self.process_decrypt_failure_report(report).await;
843 }
844 }
845 }
846
847 async fn process_decrypt_fallback(&mut self, fallback: DecryptFallback) {
848 let plaintext = &fallback.packet_data[fallback.fmp_plaintext_offset
849 ..fallback.fmp_plaintext_offset + fallback.fmp_plaintext_len];
850 self.process_authentic_fmp_plaintext(AuthenticatedFmpPlaintext::new(
851 fallback.source_peer,
852 fallback.transport_id,
853 &fallback.remote_addr,
854 fallback.timestamp_ms,
855 fallback.packet_len,
856 fallback.fmp_counter,
857 fallback.fmp_flags,
858 plaintext,
859 ))
860 .await;
861 }
862
863 async fn process_decrypt_failure_report(&mut self, report: DecryptFailureReport) {
864 debug!(
865 peer = %self.peer_display_name(report.source_peer.node_addr()),
866 counter = report.fmp_counter,
867 replay_highest = report.fmp_replay_highest,
868 "Worker FMP AEAD decryption failed"
869 );
870 self.handle_decrypt_failure_report(&report).await;
871 }
872
873 async fn drain_decrypt_priority_fallback(
880 &mut self,
881 priority_rx: &mut Receiver<DecryptWorkerEvent>,
882 first_event: Option<DecryptWorkerEvent>,
883 budget: usize,
884 ) -> usize {
885 self.begin_endpoint_event_batch();
886 let mut drain = SingleLaneDrainCursor::new(first_event, budget);
887 while let Some(event) = drain.next(priority_rx) {
888 self.process_decrypt_worker_event(event).await;
889 }
890 let drained = drain.drained();
891 self.finish_endpoint_event_batch();
892 drained
893 }
894
895 async fn drain_decrypt_fallback(
901 &mut self,
902 rx: &mut DecryptWorkerFallbackReceivers,
903 first_priority_event: Option<DecryptWorkerEvent>,
904 first_authenticated_bulk_event: Option<DecryptWorkerEvent>,
905 first_bulk_event: Option<DecryptWorkerEvent>,
906 budget: usize,
907 ) -> usize {
908 self.begin_endpoint_event_batch();
909 let mut drain = DecryptReturnDrainCursor::new(
910 first_priority_event,
911 first_authenticated_bulk_event,
912 first_bulk_event,
913 budget,
914 );
915 while let Some(event) =
916 drain.next(&mut rx.priority, &mut rx.authenticated_bulk, &mut rx.bulk)
917 {
918 rx.release_dequeued_event(&event);
919 let extra = event.packet_count().saturating_sub(1);
920 self.process_decrypt_worker_event(event).await;
921 drain.charge_extra(extra);
922 }
923 let drained = drain.drained();
924 self.finish_endpoint_event_batch();
925 drained
926 }
927
928 #[cfg(test)]
932 pub(in crate::node) async fn process_packet(&mut self, packet: ReceivedPacket) {
933 let action = self.begin_process_packet(packet);
934 self.finish_packet_process(action).await;
935 }
936
937 fn begin_process_packet(&mut self, packet: ReceivedPacket) -> PacketProcessAction {
938 let timer = crate::perf_profile::Timer::start(crate::perf_profile::Stage::ProcessPacket);
939 let priority_sized = packet.is_priority_sized();
940 let priority_count = u64::from(priority_sized);
941 let bulk_count = u64::from(!priority_sized);
942 crate::perf_profile::record_since_split_count(
943 crate::perf_profile::Stage::TransportQueueWait,
944 crate::perf_profile::Stage::TransportPriorityQueueWait,
945 crate::perf_profile::Stage::TransportBulkQueueWait,
946 packet.trace_enqueued_at,
947 1,
948 priority_count,
949 bulk_count,
950 );
951 crate::perf_profile::record_since_split_count(
952 crate::perf_profile::Stage::TransportRxLoopWait,
953 crate::perf_profile::Stage::TransportPriorityRxLoopWait,
954 crate::perf_profile::Stage::TransportBulkRxLoopWait,
955 packet.trace_rx_loop_owned_at,
956 1,
957 priority_count,
958 bulk_count,
959 );
960 if is_punch_packet(&packet.data) {
961 trace!(
962 transport_id = %packet.transport_id,
963 remote_addr = %packet.remote_addr,
964 bytes = packet.data.len(),
965 "Dropping stray punch probe/ack in FMP rx loop"
966 );
967 return PacketProcessAction::Done;
968 }
969 if packet.data.len() < COMMON_PREFIX_SIZE {
970 return PacketProcessAction::Done; }
972
973 let prefix = match CommonPrefix::parse(&packet.data) {
974 Some(p) => p,
975 None => return PacketProcessAction::Done, };
977 if matches!(prefix.phase, PHASE_MSG1 | PHASE_MSG2) {
978 debug!(
979 transport_id = %packet.transport_id,
980 remote_addr = %packet.remote_addr,
981 bytes = packet.data.len(),
982 phase = prefix.phase,
983 version = prefix.version,
984 "FMP handshake packet dispatch"
985 );
986 } else {
987 trace!(
988 transport_id = %packet.transport_id,
989 remote_addr = %packet.remote_addr,
990 bytes = packet.data.len(),
991 phase = prefix.phase,
992 version = prefix.version,
993 "FMP packet dispatch"
994 );
995 }
996
997 if prefix.version != FMP_VERSION {
998 debug!(
999 version = prefix.version,
1000 transport_id = %packet.transport_id,
1001 "Unknown FMP version, dropping"
1002 );
1003
1004 let looks_like_fmp_phase =
1012 matches!(prefix.phase, PHASE_ESTABLISHED | PHASE_MSG1 | PHASE_MSG2);
1013 if looks_like_fmp_phase
1014 && self.bootstrap_transports.contains(&packet.transport_id)
1015 && let Some(npub) = self.bootstrap_transports.peer_npub(&packet.transport_id)
1016 && let Some(handle) = self.nostr_discovery_handle()
1017 {
1018 let now_ms = Self::now_ms();
1019 let cooldown_secs = handle.protocol_mismatch_cooldown_secs();
1020 if handle.record_protocol_mismatch(npub, now_ms) {
1021 warn!(
1022 peer_npub = %npub,
1023 transport_id = %packet.transport_id,
1024 peer_version = prefix.version,
1025 our_version = FMP_VERSION,
1026 cooldown_secs,
1027 "Nostr-discovered peer speaks a different FMP version; suppressing retraversal"
1028 );
1029 }
1030 }
1031 return PacketProcessAction::Done;
1032 }
1033
1034 match prefix.phase {
1035 PHASE_ESTABLISHED => match self.try_prepare_encrypted_frame_for_worker(packet) {
1036 EncryptedFrameFastPath::Dispatch(job) => PacketProcessAction::DecryptJob { job },
1037 EncryptedFrameFastPath::Dropped => PacketProcessAction::Done,
1038 EncryptedFrameFastPath::Slow(packet) => {
1039 PacketProcessAction::EncryptedSlow { packet, timer }
1040 }
1041 },
1042 PHASE_MSG1 => PacketProcessAction::Msg1 { packet, timer },
1043 PHASE_MSG2 => PacketProcessAction::Msg2 { packet, timer },
1044 _ => {
1045 debug!(
1046 phase = prefix.phase,
1047 transport_id = %packet.transport_id,
1048 "Unknown FMP phase, dropping"
1049 );
1050 PacketProcessAction::Done
1051 }
1052 }
1053 }
1054
1055 async fn finish_packet_process(&mut self, action: PacketProcessAction) {
1056 match action {
1057 PacketProcessAction::Done => {}
1058 PacketProcessAction::DecryptJob { job } => {
1059 if let Some(workers) = self.decrypt_workers.as_ref() {
1060 workers.dispatch_job(job);
1061 }
1062 }
1063 PacketProcessAction::EncryptedSlow {
1064 packet,
1065 timer: _timer,
1066 } => {
1067 self.handle_encrypted_frame_slow(packet).await;
1068 }
1069 PacketProcessAction::Msg1 {
1070 packet,
1071 timer: _timer,
1072 } => {
1073 self.handle_msg1(packet).await;
1074 }
1075 PacketProcessAction::Msg2 {
1076 packet,
1077 timer: _timer,
1078 } => {
1079 self.handle_msg2(packet).await;
1080 }
1081 }
1082 }
1083
1084 fn flush_decrypt_job_batcher(&self, batcher: &mut DecryptJobBatcher) {
1085 if let Some(workers) = self.decrypt_workers.as_ref() {
1086 batcher.flush(workers);
1087 }
1088 }
1089}