1use crate::control::queries;
4use crate::control::{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::handlers::session::EndpointCommandDrainStages;
12use crate::node::wire::{
13 COMMON_PREFIX_SIZE, CommonPrefix, FMP_VERSION, PHASE_ESTABLISHED, PHASE_MSG1, PHASE_MSG2,
14};
15use crate::node::{AuthenticatedFmpPlaintext, Node, NodeEndpointCommand, NodeError};
16use crate::transport::PacketRx;
17use crate::transport::ReceivedPacket;
18use crate::upper::tun::TunOutboundRx;
19use std::time::{Duration, Instant};
20use tokio::sync::mpsc::Receiver;
21use tracing::{debug, info, trace, warn};
22
23mod budget;
24mod drain;
25
26#[cfg(test)]
27mod tests;
28
29use budget::*;
30use drain::*;
31
32#[derive(Copy, Clone)]
33enum EndpointCommandDrainSource {
34 DirectPriority,
35 DirectBulk,
36 SidePacket,
37 SideDecryptPriority,
38 SideAuthenticatedBulk,
39 SideDecryptBulk,
40 MaintenancePre,
41 MaintenancePost,
42}
43
44impl EndpointCommandDrainSource {
45 fn aggregate_event(self) -> crate::perf_profile::Event {
46 match self {
47 Self::DirectPriority => {
48 crate::perf_profile::Event::RxLoopEndpointCommandDrainDirectPriority
49 }
50 Self::DirectBulk => crate::perf_profile::Event::RxLoopEndpointCommandDrainDirectBulk,
51 Self::SidePacket
52 | Self::SideDecryptPriority
53 | Self::SideAuthenticatedBulk
54 | Self::SideDecryptBulk => crate::perf_profile::Event::RxLoopEndpointCommandDrainSide,
55 Self::MaintenancePre => {
56 crate::perf_profile::Event::RxLoopEndpointCommandDrainMaintenancePre
57 }
58 Self::MaintenancePost => {
59 crate::perf_profile::Event::RxLoopEndpointCommandDrainMaintenancePost
60 }
61 }
62 }
63
64 fn detail_event(self) -> Option<crate::perf_profile::Event> {
65 match self {
66 Self::SidePacket => {
67 Some(crate::perf_profile::Event::RxLoopEndpointCommandDrainSidePacket)
68 }
69 Self::SideDecryptPriority => {
70 Some(crate::perf_profile::Event::RxLoopEndpointCommandDrainSideDecryptPriority)
71 }
72 Self::SideAuthenticatedBulk => {
73 Some(crate::perf_profile::Event::RxLoopEndpointCommandDrainSideAuthenticatedBulk)
74 }
75 Self::SideDecryptBulk => {
76 Some(crate::perf_profile::Event::RxLoopEndpointCommandDrainSideDecryptBulk)
77 }
78 Self::DirectPriority
79 | Self::DirectBulk
80 | Self::MaintenancePre
81 | Self::MaintenancePost => None,
82 }
83 }
84
85 fn wait_stages(self) -> EndpointCommandDrainStages {
86 match self {
87 Self::DirectPriority => EndpointCommandDrainStages::aggregate(
88 crate::perf_profile::Stage::EndpointCommandDirectPriorityWait,
89 ),
90 Self::DirectBulk => EndpointCommandDrainStages::aggregate(
91 crate::perf_profile::Stage::EndpointCommandDirectBulkWait,
92 ),
93 Self::SidePacket => EndpointCommandDrainStages::with_detail(
94 crate::perf_profile::Stage::EndpointCommandSideWait,
95 crate::perf_profile::Stage::EndpointCommandSidePacketWait,
96 ),
97 Self::SideDecryptPriority => EndpointCommandDrainStages::with_detail(
98 crate::perf_profile::Stage::EndpointCommandSideWait,
99 crate::perf_profile::Stage::EndpointCommandSideDecryptPriorityWait,
100 ),
101 Self::SideAuthenticatedBulk => EndpointCommandDrainStages::with_detail(
102 crate::perf_profile::Stage::EndpointCommandSideWait,
103 crate::perf_profile::Stage::EndpointCommandSideAuthenticatedBulkWait,
104 ),
105 Self::SideDecryptBulk => EndpointCommandDrainStages::with_detail(
106 crate::perf_profile::Stage::EndpointCommandSideWait,
107 crate::perf_profile::Stage::EndpointCommandSideDecryptBulkWait,
108 ),
109 Self::MaintenancePre => EndpointCommandDrainStages::aggregate(
110 crate::perf_profile::Stage::EndpointCommandMaintenancePreWait,
111 ),
112 Self::MaintenancePost => EndpointCommandDrainStages::aggregate(
113 crate::perf_profile::Stage::EndpointCommandMaintenancePostWait,
114 ),
115 }
116 }
117}
118
119fn transport_should_preempt_non_packet(packet_rx: Option<&PacketRx>, drained: usize) -> bool {
120 drained > 0
121 && packet_rx.is_some_and(|packet_rx| {
122 transport_packets_preempt_non_packet(packet_rx.ready_packets())
123 })
124}
125
126impl Node {
127 pub async fn run_rx_loop(&mut self) -> Result<(), NodeError> {
147 let mut packet_rx = self.packet_rx.take().ok_or(NodeError::NotStarted)?;
148
149 let (mut tun_outbound_rx, _tun_guard) = match self.tun_outbound_rx.take() {
153 Some(rx) => (rx, None),
154 None => {
155 let (tx, rx) = tokio::sync::mpsc::channel(1);
156 (rx, Some(tx))
157 }
158 };
159
160 let (mut dns_identity_rx, _dns_guard) = match self.dns_identity_rx.take() {
163 Some(rx) => (rx, None),
164 None => {
165 let (tx, rx) = tokio::sync::mpsc::channel(1);
166 (rx, Some(tx))
167 }
168 };
169
170 let (mut endpoint_priority_command_rx, _endpoint_priority_command_guard) =
173 match self.endpoint_priority_command_rx.take() {
174 Some(rx) => (rx, None),
175 None => {
176 let (tx, rx) = tokio::sync::mpsc::channel(1);
177 (rx, Some(tx))
178 }
179 };
180 let (mut endpoint_command_rx, _endpoint_command_guard) =
181 match self.endpoint_command_rx.take() {
182 Some(rx) => (rx, None),
183 None => {
184 let (tx, rx) = tokio::sync::mpsc::channel(1);
185 (rx, Some(tx))
186 }
187 };
188
189 let (mut decrypt_fallback_rx, _decrypt_fallback_guard) =
193 match self.decrypt_fallback_rx.take() {
194 Some(rx) => (rx, None),
195 None => {
196 let (tx, rx) = crate::node::decrypt_worker::decrypt_worker_fallback_channels();
197 (rx, Some(tx))
198 }
199 };
200
201 let mut tick =
202 tokio::time::interval(Duration::from_secs(self.config.node.tick_interval_secs));
203 tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
204 let mut maintenance_state = RxLoopMaintenanceState::default();
205
206 let (control_tx, mut control_rx) =
208 tokio::sync::mpsc::channel::<crate::control::ControlMessage>(32);
209
210 if self.config.node.control.enabled {
211 let config = self.config.node.control.clone();
212 let tx = control_tx.clone();
213 tokio::spawn(async move {
214 match ControlSocket::bind(&config) {
215 Ok(socket) => {
216 socket.accept_loop(tx).await;
217 }
218 Err(e) => {
219 warn!(error = %e, "Failed to bind control socket");
220 }
221 }
222 });
223 }
224 drop(control_tx);
226
227 info!("RX event loop started");
228 crate::perf_profile::maybe_spawn_reporter();
230 tick.tick().await;
234 let mut endpoint_priority_preempted_transport = false;
235
236 loop {
237 tokio::select! {
238 biased;
239 Some(event) = decrypt_fallback_rx.priority.recv() => {
250 let fallback_drained = self.drain_decrypt_priority_fallback(
251 &mut decrypt_fallback_rx.priority,
252 Some(event),
253 PRIORITY_FALLBACK_DRAIN_BUDGET,
254 Some(&packet_rx),
255 ).await;
256 let side_drained = self.drain_rx_loop_side_queues(
257 &mut tun_outbound_rx,
258 &mut endpoint_priority_command_rx,
259 &mut endpoint_command_rx,
260 SIDE_QUEUE_INTERLEAVE_BUDGET,
261 EndpointCommandDrainSource::SideDecryptPriority,
262 Some(&packet_rx),
263 ).await;
264 if fallback_drained > 0 || side_drained.has_drained() {
265 maintenance_state.record_data_activity(Instant::now());
266 }
267 }
268 _ = tick.tick() => {
274 let drained = self.drain_rx_loop_data_queues(
275 &mut packet_rx,
276 &mut decrypt_fallback_rx,
277 &mut tun_outbound_rx,
278 &mut endpoint_priority_command_rx,
279 &mut endpoint_command_rx,
280 NON_PACKET_DRAIN_BUDGET,
281 EndpointCommandDrainSource::MaintenancePre,
282 ).await;
283 if drained.packets > 0 {
284 endpoint_priority_preempted_transport = false;
285 }
286 if drained.has_drained() {
287 maintenance_state.record_data_activity(Instant::now());
288 debug!(
289 drained = drained.total(),
290 drained_packets = drained.packets,
291 drained_tun = drained.tun,
292 drained_endpoint = drained.endpoint,
293 "Drained queued packets before rx-loop maintenance"
294 );
295 }
296 let maintenance_plan = maintenance_state.plan_maintenance(
297 drained,
298 Instant::now(),
299 RX_LOOP_RECENT_DATA_ACTIVITY_WINDOW,
300 RX_LOOP_SLOW_MAINTENANCE_IDLE_TIMEOUT,
301 RX_LOOP_SLOW_MAINTENANCE_BUSY_TIMEOUT,
302 );
303 self.run_connected_udp_activation_tick(connected_udp_activation_timeout(
304 maintenance_plan.data_pressure(),
305 )).await;
306
307 let slow_timed_out = self.run_rx_loop_maintenance_tick(
308 maintenance_plan,
309 ).await;
310 maintenance_state.record_maintenance_result(
311 maintenance_plan.data_pressure(),
312 slow_timed_out,
313 );
314
315 let post_drained = self.drain_rx_loop_data_queues(
316 &mut packet_rx,
317 &mut decrypt_fallback_rx,
318 &mut tun_outbound_rx,
319 &mut endpoint_priority_command_rx,
320 &mut endpoint_command_rx,
321 PACKET_DRAIN_BUDGET,
322 EndpointCommandDrainSource::MaintenancePost,
323 ).await;
324 if post_drained.packets > 0 {
325 endpoint_priority_preempted_transport = false;
326 }
327 if post_drained.has_drained() {
328 maintenance_state.record_data_activity(Instant::now());
329 debug!(
330 drained = post_drained.total(),
331 drained_packets = post_drained.packets,
332 drained_tun = post_drained.tun,
333 drained_endpoint = post_drained.endpoint,
334 "Drained queued packets after rx-loop maintenance"
335 );
336 }
337 }
338 Some(command) = endpoint_priority_command_rx.recv(),
339 if endpoint_priority_commands_preempt_packet_rx(
340 packet_rx.ready_packets(),
341 packet_rx.priority_ready_packets(),
342 endpoint_priority_preempted_transport,
343 ) =>
344 {
345 let drained = self.drain_endpoint_priority_commands(
346 &mut endpoint_priority_command_rx,
347 Some(command),
348 ENDPOINT_COMMAND_DRAIN_BUDGET,
349 EndpointCommandDrainSource::DirectPriority,
350 Some(&packet_rx),
351 ).await;
352 endpoint_priority_preempted_transport = packet_rx.ready_packets() > 0;
353 if drained > 0 {
354 maintenance_state.record_data_activity(Instant::now());
355 }
356 }
357 Some(event) = decrypt_fallback_rx.authenticated_bulk.recv(),
358 if authenticated_bulk_preempts_packet_rx(packet_rx.ready_packets()) =>
359 {
360 let fallback_drained = self.drain_decrypt_fallback(
361 &mut decrypt_fallback_rx,
362 None,
363 Some(event),
364 None,
365 NON_PACKET_DRAIN_BUDGET,
366 Some(&packet_rx),
367 ).await;
368 let side_drained = self.drain_rx_loop_side_queues(
369 &mut tun_outbound_rx,
370 &mut endpoint_priority_command_rx,
371 &mut endpoint_command_rx,
372 SIDE_QUEUE_INTERLEAVE_BUDGET,
373 EndpointCommandDrainSource::SideAuthenticatedBulk,
374 Some(&packet_rx),
375 ).await;
376 if fallback_drained > 0 || side_drained.has_drained() {
377 maintenance_state.record_data_activity(Instant::now());
378 }
379 }
380 packet = packet_rx.recv() => {
381 match packet {
382 Some(p) => {
383 endpoint_priority_preempted_transport = false;
384 let drained = self.drain_packet_rx(
385 &mut packet_rx,
386 &mut decrypt_fallback_rx,
387 Some(RxLoopSideQueues {
388 tun_outbound_rx: &mut tun_outbound_rx,
389 endpoint_priority_command_rx: &mut endpoint_priority_command_rx,
390 endpoint_command_rx: &mut endpoint_command_rx,
391 }),
392 Some(p),
393 PACKET_DRAIN_BUDGET,
394 ).await;
395 if drained > 0 {
396 maintenance_state.record_data_activity(Instant::now());
397 }
398 }
399 None => break, }
401 }
402 Some(event) = decrypt_fallback_rx.bulk.recv() => {
403 let fallback_plan = fallback_drain_plan(
404 packet_rx.priority_ready_packets(),
405 decrypt_fallback_rx.bulk_queued_packets(),
406 );
407 let fallback_drained = self.drain_decrypt_fallback(
408 &mut decrypt_fallback_rx,
409 None,
410 None,
411 Some(event),
412 fallback_plan.trailing_budget,
413 Some(&packet_rx),
414 ).await;
415 let side_drained = self.drain_rx_loop_side_queues(
416 &mut tun_outbound_rx,
417 &mut endpoint_priority_command_rx,
418 &mut endpoint_command_rx,
419 SIDE_QUEUE_INTERLEAVE_BUDGET,
420 EndpointCommandDrainSource::SideDecryptBulk,
421 Some(&packet_rx),
422 ).await;
423 if fallback_drained > 0 || side_drained.has_drained() {
424 maintenance_state.record_data_activity(Instant::now());
425 }
426 }
427 Some(ipv6_packet) = tun_outbound_rx.recv() => {
428 let drained = self.drain_tun_outbound(
429 &mut tun_outbound_rx,
430 Some(ipv6_packet),
431 NON_PACKET_DRAIN_BUDGET,
432 Some(&packet_rx),
433 ).await;
434 if drained > 0 {
435 maintenance_state.record_data_activity(Instant::now());
436 }
437 }
438 Some(identity) = dns_identity_rx.recv() => {
439 debug!(
440 node_addr = %identity.node_addr,
441 "Registering identity from DNS resolution"
442 );
443 self.register_identity(identity.node_addr, identity.pubkey);
444 }
445 Some(command) = endpoint_command_rx.recv() => {
446 let drained = self.drain_endpoint_commands(
447 &mut endpoint_priority_command_rx,
448 &mut endpoint_command_rx,
449 None,
450 Some(command),
451 ENDPOINT_COMMAND_DRAIN_BUDGET,
452 EndpointCommandDrainSource::DirectBulk,
453 Some(&packet_rx),
454 ).await;
455 if drained > 0 {
456 maintenance_state.record_data_activity(Instant::now());
457 }
458 }
459 Some((request, response_tx)) = control_rx.recv() => {
460 let response = if request.command.starts_with("show_") {
461 queries::dispatch(self, &request.command, request.params.as_ref())
462 } else {
463 commands::dispatch(
464 self,
465 &request.command,
466 request.params.as_ref(),
467 ).await
468 };
469 let _ = response_tx.send(response);
470 }
471 }
472 }
473
474 info!("RX event loop stopped (channel closed)");
475 Ok(())
476 }
477
478 #[allow(clippy::too_many_arguments)]
479 async fn drain_rx_loop_data_queues(
480 &mut self,
481 packet_rx: &mut PacketRx,
482 decrypt_fallback_rx: &mut DecryptWorkerFallbackReceivers,
483 tun_outbound_rx: &mut TunOutboundRx,
484 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
485 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
486 budget: usize,
487 endpoint_command_drain_source: EndpointCommandDrainSource,
488 ) -> RxLoopDataDrainStats {
489 let drained_packets = self
490 .drain_packet_rx(packet_rx, decrypt_fallback_rx, None, None, budget)
491 .await;
492 let non_packet_budget = non_packet_drain_budget(budget);
493 let drained_tun = self
494 .drain_tun_outbound(tun_outbound_rx, None, non_packet_budget, Some(packet_rx))
495 .await;
496 let drained_endpoint = self
497 .drain_endpoint_commands(
498 endpoint_priority_command_rx,
499 endpoint_command_rx,
500 None,
501 None,
502 non_packet_budget,
503 endpoint_command_drain_source,
504 Some(packet_rx),
505 )
506 .await;
507 RxLoopDataDrainStats::new(drained_packets, drained_tun, drained_endpoint)
508 }
509
510 async fn drain_packet_rx(
511 &mut self,
512 packet_rx: &mut PacketRx,
513 decrypt_fallback_rx: &mut DecryptWorkerFallbackReceivers,
514 mut side_queues: Option<RxLoopSideQueues<'_>>,
515 first_packet: Option<ReceivedPacket>,
516 budget: usize,
517 ) -> usize {
518 self.begin_endpoint_event_batch();
524 let side_queue_interleave_every = side_queues
525 .as_ref()
526 .map(|side_queues| {
527 side_queue_interleave_interval(rx_loop_endpoint_commands_have_ready(side_queues))
528 })
529 .unwrap_or(0);
530 let mut fallback_plan = fallback_drain_plan(
531 packet_rx.priority_ready_packets(),
532 decrypt_fallback_rx.bulk_queued_packets(),
533 );
534 let mut drain = PacketDrainCursor::new(
535 first_packet,
536 budget,
537 fallback_plan.interleave_every,
538 side_queue_interleave_every,
539 );
540 let mut decrypt_jobs = DecryptJobBatcher::new();
541 while let Some(action) = drain.next(packet_rx) {
542 match action {
543 PacketDrainAction::Packet(packet) => {
544 let action = self.begin_process_packet(packet);
545 match action {
546 PacketProcessAction::DecryptJob { job } => {
547 if let Some(workers) = self.decrypt_workers.as_ref() {
548 decrypt_jobs.push(workers, job);
549 }
550 }
551 PacketProcessAction::Done => {}
552 action => {
553 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
554 self.finish_packet_process(action).await;
555 }
556 }
557 }
558 PacketDrainAction::InterleaveFallback => {
559 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
560 fallback_plan = fallback_drain_plan(
561 packet_rx.priority_ready_packets(),
562 decrypt_fallback_rx.bulk_queued_packets(),
563 );
564 drain.reset_fallback_interleave_every(fallback_plan.interleave_every);
565 let drained = if decrypt_fallback_has_ready(decrypt_fallback_rx) {
566 self.drain_decrypt_fallback(
567 decrypt_fallback_rx,
568 None,
569 None,
570 None,
571 fallback_plan.interleave_budget,
572 Some(&*packet_rx),
573 )
574 .await
575 } else {
576 0
577 };
578 if drained == 0 {
579 drain.refund_empty_interleave_turn();
580 }
581 }
582 PacketDrainAction::InterleaveSideQueues => {
583 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
584 let drained = if let Some(side_queues) = side_queues.as_mut() {
585 if rx_loop_side_queues_have_ready(side_queues) {
586 self.drain_rx_loop_side_queues(
587 side_queues.tun_outbound_rx,
588 side_queues.endpoint_priority_command_rx,
589 side_queues.endpoint_command_rx,
590 SIDE_QUEUE_INTERLEAVE_BUDGET,
591 EndpointCommandDrainSource::SidePacket,
592 None,
593 )
594 .await
595 } else {
596 RxLoopDataDrainStats::default()
597 }
598 } else {
599 RxLoopDataDrainStats::default()
600 };
601 if !drained.has_drained() {
602 drain.refund_empty_interleave_turn();
603 drain.reset_side_queue_interleave_every(SIDE_QUEUE_INTERLEAVE_EVERY);
604 } else if let Some(side_queues) = side_queues.as_ref() {
605 drain.reset_side_queue_interleave_every(side_queue_interleave_interval(
606 rx_loop_endpoint_commands_have_ready(side_queues),
607 ));
608 }
609 }
610 }
611 }
612
613 self.flush_decrypt_job_batcher(&mut decrypt_jobs);
614 let drained = drain.drained();
615 if drained > 0 {
616 fallback_plan = fallback_drain_plan(
617 packet_rx.priority_ready_packets(),
618 decrypt_fallback_rx.bulk_queued_packets(),
619 );
620 self.drain_decrypt_fallback(
625 decrypt_fallback_rx,
626 None,
627 None,
628 None,
629 fallback_plan.trailing_budget.min(budget),
630 Some(&*packet_rx),
631 )
632 .await;
633 self.finish_endpoint_event_batch();
634 } else {
635 self.finish_endpoint_event_batch();
636 }
637 drained
638 }
639
640 async fn drain_rx_loop_side_queues(
641 &mut self,
642 tun_outbound_rx: &mut TunOutboundRx,
643 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
644 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
645 budget: usize,
646 endpoint_command_drain_source: EndpointCommandDrainSource,
647 packet_rx: Option<&PacketRx>,
648 ) -> RxLoopDataDrainStats {
649 let (endpoint_budget, tun_budget) = split_side_queue_budget(budget);
650 let mut drained_endpoint = self
651 .drain_endpoint_commands(
652 endpoint_priority_command_rx,
653 endpoint_command_rx,
654 None,
655 None,
656 endpoint_budget,
657 endpoint_command_drain_source,
658 packet_rx,
659 )
660 .await;
661 let mut drained_tun = self
662 .drain_tun_outbound(tun_outbound_rx, None, tun_budget, packet_rx)
663 .await;
664
665 let endpoint_remainder = remaining_side_queue_budget(endpoint_budget, drained_endpoint);
666 let tun_remainder = remaining_side_queue_budget(tun_budget, drained_tun);
667 if endpoint_remainder > 0 && !tun_outbound_rx.is_empty() {
668 drained_tun += self
669 .drain_tun_outbound(tun_outbound_rx, None, endpoint_remainder, packet_rx)
670 .await;
671 }
672 if tun_remainder > 0
673 && (!endpoint_priority_command_rx.is_empty() || !endpoint_command_rx.is_empty())
674 {
675 drained_endpoint += self
676 .drain_endpoint_commands(
677 endpoint_priority_command_rx,
678 endpoint_command_rx,
679 None,
680 None,
681 tun_remainder,
682 endpoint_command_drain_source,
683 packet_rx,
684 )
685 .await;
686 }
687
688 RxLoopDataDrainStats::new(0, drained_tun, drained_endpoint)
689 }
690
691 async fn drain_tun_outbound(
692 &mut self,
693 tun_outbound_rx: &mut TunOutboundRx,
694 first_packet: Option<Vec<u8>>,
695 budget: usize,
696 packet_rx: Option<&PacketRx>,
697 ) -> usize {
698 let mut drain = SingleLaneDrainCursor::new(first_packet, budget);
699 while let Some(packet) = drain.next(tun_outbound_rx) {
700 self.handle_tun_outbound(packet).await;
701 if transport_should_preempt_non_packet(packet_rx, drain.drained()) {
702 break;
703 }
704 }
705
706 drain.drained()
707 }
708
709 #[allow(clippy::too_many_arguments)]
710 async fn drain_endpoint_commands(
711 &mut self,
712 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
713 endpoint_command_rx: &mut Receiver<NodeEndpointCommand>,
714 first_priority_command: Option<NodeEndpointCommand>,
715 first_bulk_command: Option<NodeEndpointCommand>,
716 budget: usize,
717 source: EndpointCommandDrainSource,
718 packet_rx: Option<&PacketRx>,
719 ) -> usize {
720 let mut drain =
721 PriorityBulkDrainCursor::new(first_priority_command, first_bulk_command, budget);
722 while let Some(command) = drain.next(endpoint_priority_command_rx, endpoint_command_rx) {
723 let drain_cost = command.drain_cost();
724 self.handle_endpoint_data_command(command, source.wait_stages())
725 .await;
726 drain.charge_extra(drain_cost.saturating_sub(1));
727 if transport_should_preempt_non_packet(packet_rx, drain.drained()) {
728 break;
729 }
730 }
731
732 let drained = drain.drained();
733 crate::perf_profile::record_event_count(source.aggregate_event(), drained as u64);
734 if let Some(detail_event) = source.detail_event() {
735 crate::perf_profile::record_event_count(detail_event, drained as u64);
736 }
737 drained
738 }
739
740 async fn drain_endpoint_priority_commands(
741 &mut self,
742 endpoint_priority_command_rx: &mut Receiver<NodeEndpointCommand>,
743 first_command: Option<NodeEndpointCommand>,
744 budget: usize,
745 source: EndpointCommandDrainSource,
746 packet_rx: Option<&PacketRx>,
747 ) -> usize {
748 let mut drain = SingleLaneDrainCursor::new(first_command, budget);
749 while let Some(command) = drain.next(endpoint_priority_command_rx) {
750 let drain_cost = command.drain_cost();
751 self.handle_endpoint_data_command(command, source.wait_stages())
752 .await;
753 drain.charge_extra(drain_cost.saturating_sub(1));
754 if transport_should_preempt_non_packet(packet_rx, drain.drained()) {
755 break;
756 }
757 }
758
759 let drained = drain.drained();
760 crate::perf_profile::record_event_count(source.aggregate_event(), drained as u64);
761 if let Some(detail_event) = source.detail_event() {
762 crate::perf_profile::record_event_count(detail_event, drained as u64);
763 }
764 drained
765 }
766
767 async fn run_rx_loop_maintenance_tick(&mut self, plan: RxLoopMaintenancePlan) -> bool {
768 self.check_timeouts();
769 let now_ms = Self::now_ms();
770 self.check_link_heartbeats().await;
774 self.reload_peer_acl();
775 self.resend_pending_rekeys(now_ms).await;
776 self.resend_pending_session_handshakes(now_ms).await;
777 self.resend_pending_session_msg3(now_ms).await;
778 self.purge_idle_sessions(now_ms);
779 self.purge_learned_routes(now_ms);
780 self.check_mmp_reports().await;
781 self.check_session_mmp_reports().await;
782 self.check_rekey().await;
783 self.check_session_rekey().await;
784 self.sample_transport_congestion();
785
786 let Some(slow_timeout) = plan.slow_timeout() else {
787 crate::perf_profile::record_event(
788 crate::perf_profile::Event::RxLoopSlowMaintenanceSkipped,
789 );
790 return false;
791 };
792
793 if tokio::time::timeout(slow_timeout, self.run_rx_loop_slow_maintenance_tick(now_ms))
794 .await
795 .is_err()
796 {
797 crate::perf_profile::record_event(
798 crate::perf_profile::Event::RxLoopSlowMaintenanceTimeout,
799 );
800 self.mark_rx_loop_maintenance_timeout();
801 warn!(
802 timeout_ms = slow_timeout.as_millis() as u64,
803 data_pressure = plan.data_pressure(),
804 "RX loop slow maintenance timed out; continuing packet processing"
805 );
806 return true;
807 }
808 false
809 }
810
811 async fn run_connected_udp_activation_tick(&mut self, timeout: Duration) {
812 if tokio::time::timeout(timeout, self.activate_connected_udp_sessions())
813 .await
814 .is_err()
815 {
816 debug!(
817 timeout_ms = timeout.as_millis() as u64,
818 "connected UDP activation timed out; will retry on a later tick"
819 );
820 }
821 }
822
823 async fn run_rx_loop_slow_maintenance_tick(&mut self, now_ms: u64) {
824 if let Some(delay) = rx_loop_slow_maintenance_fault_delay() {
825 tokio::time::sleep(delay).await;
826 }
827
828 self.resend_pending_handshakes(now_ms).await;
837 self.check_pending_lookups(now_ms).await;
838 self.poll_pending_connects().await;
839 self.process_pending_retries(now_ms).await;
840 self.poll_transport_discovery().await;
841 self.poll_nostr_discovery().await;
842 self.poll_lan_discovery().await;
843 self.poll_local_instance_discovery().await;
844 self.check_tree_state().await;
845 self.check_bloom_state().await;
846 self.compute_mesh_size();
847 self.record_stats_history();
848 }
849
850 async fn process_decrypt_worker_event(&mut self, event: DecryptWorkerEvent) {
855 event.record_queue_wait();
856 match event {
857 DecryptWorkerEvent::Plaintext(fallback) => {
858 self.process_decrypt_fallback(fallback).await;
859 }
860 DecryptWorkerEvent::PlaintextBatch(fallbacks) => {
861 for fallback in fallbacks {
862 self.process_decrypt_fallback(fallback).await;
863 }
864 }
865 DecryptWorkerEvent::AuthenticatedFmpReceive(receive) => {
866 self.process_authenticated_fmp_receive_from_worker(receive);
867 }
868 DecryptWorkerEvent::DirectFmpEndpointData(endpoint) => {
869 self.process_direct_fmp_endpoint_data_from_worker(endpoint)
870 .await;
871 }
872 DecryptWorkerEvent::DirectFmpEndpointDataBatch(endpoints) => {
873 self.process_direct_fmp_endpoint_data_batch_from_worker(endpoints)
874 .await;
875 }
876 DecryptWorkerEvent::AuthenticatedSession(session) => {
877 self.process_authenticated_session_from_worker(session)
878 .await;
879 }
880 DecryptWorkerEvent::DirectSessionCommit(commit) => {
881 self.process_direct_session_commit_from_worker(commit).await;
882 }
883 DecryptWorkerEvent::DirectSessionCommitBatch(commits) => {
884 for commit in commits {
885 self.process_direct_session_commit_from_worker(commit).await;
886 }
887 }
888 DecryptWorkerEvent::DirectSessionData(direct) => {
889 self.process_direct_session_data_from_worker(direct).await;
890 }
891 DecryptWorkerEvent::FspDecryptFailure(report) => {
892 self.process_fsp_decrypt_failure_from_worker(report).await;
893 }
894 DecryptWorkerEvent::DecryptFailure(report) => {
895 self.process_decrypt_failure_report(report).await;
896 }
897 }
898 }
899
900 async fn process_decrypt_fallback(&mut self, fallback: DecryptFallback) {
901 let plaintext = &fallback.packet_data[fallback.fmp_plaintext_offset
902 ..fallback.fmp_plaintext_offset + fallback.fmp_plaintext_len];
903 self.process_authentic_fmp_plaintext(AuthenticatedFmpPlaintext::new(
904 fallback.source_peer,
905 fallback.transport_id,
906 &fallback.remote_addr,
907 fallback.timestamp_ms,
908 fallback.packet_len,
909 fallback.fmp_counter,
910 fallback.fmp_flags,
911 plaintext,
912 ))
913 .await;
914 }
915
916 async fn process_decrypt_failure_report(&mut self, report: DecryptFailureReport) {
917 debug!(
918 peer = %self.peer_display_name(report.source_peer.node_addr()),
919 counter = report.fmp_counter,
920 replay_highest = report.fmp_replay_highest,
921 "Worker FMP AEAD decryption failed"
922 );
923 self.handle_decrypt_failure_report(&report).await;
924 }
925
926 async fn drain_decrypt_priority_fallback(
933 &mut self,
934 priority_rx: &mut Receiver<DecryptWorkerEvent>,
935 first_event: Option<DecryptWorkerEvent>,
936 budget: usize,
937 packet_rx: Option<&PacketRx>,
938 ) -> usize {
939 self.begin_endpoint_event_batch();
940 let mut drain = SingleLaneDrainCursor::new(first_event, budget);
941 while let Some(event) = drain.next(priority_rx) {
942 let extra = event.packet_count().saturating_sub(1);
943 self.process_decrypt_worker_event(event).await;
944 drain.charge_extra(extra);
945 if transport_should_preempt_non_packet(packet_rx, drain.drained()) {
946 break;
947 }
948 }
949 let drained = drain.drained();
950 self.finish_endpoint_event_batch();
951 drained
952 }
953
954 async fn drain_decrypt_fallback(
960 &mut self,
961 rx: &mut DecryptWorkerFallbackReceivers,
962 first_priority_event: Option<DecryptWorkerEvent>,
963 first_authenticated_bulk_event: Option<DecryptWorkerEvent>,
964 first_bulk_event: Option<DecryptWorkerEvent>,
965 budget: usize,
966 packet_rx: Option<&PacketRx>,
967 ) -> usize {
968 self.begin_endpoint_event_batch();
969 let mut drain = DecryptReturnDrainCursor::new(
970 first_priority_event,
971 first_authenticated_bulk_event,
972 first_bulk_event,
973 budget,
974 );
975 while let Some(event) =
976 drain.next(&mut rx.priority, &mut rx.authenticated_bulk, &mut rx.bulk)
977 {
978 rx.release_dequeued_event(&event);
979 let extra = event.packet_count().saturating_sub(1);
980 self.process_decrypt_worker_event(event).await;
981 drain.charge_extra(extra);
982 if transport_should_preempt_non_packet(packet_rx, drain.drained()) {
983 break;
984 }
985 }
986 let drained = drain.drained();
987 self.finish_endpoint_event_batch();
988 drained
989 }
990
991 #[cfg(test)]
995 pub(in crate::node) async fn process_packet(&mut self, packet: ReceivedPacket) {
996 let action = self.begin_process_packet(packet);
997 self.finish_packet_process(action).await;
998 }
999
1000 fn begin_process_packet(&mut self, packet: ReceivedPacket) -> PacketProcessAction {
1001 let timer = crate::perf_profile::Timer::start(crate::perf_profile::Stage::ProcessPacket);
1002 let priority_sized = packet.is_priority_sized();
1003 let priority_count = u64::from(priority_sized);
1004 let bulk_count = u64::from(!priority_sized);
1005 crate::perf_profile::record_since_split_count(
1006 crate::perf_profile::Stage::TransportQueueWait,
1007 crate::perf_profile::Stage::TransportPriorityQueueWait,
1008 crate::perf_profile::Stage::TransportBulkQueueWait,
1009 packet.trace_enqueued_at,
1010 1,
1011 priority_count,
1012 bulk_count,
1013 );
1014 crate::perf_profile::record_since_split_count(
1015 crate::perf_profile::Stage::TransportRxLoopWait,
1016 crate::perf_profile::Stage::TransportPriorityRxLoopWait,
1017 crate::perf_profile::Stage::TransportBulkRxLoopWait,
1018 packet.trace_rx_loop_owned_at,
1019 1,
1020 priority_count,
1021 bulk_count,
1022 );
1023 if is_punch_packet(&packet.data) {
1024 trace!(
1025 transport_id = %packet.transport_id,
1026 remote_addr = %packet.remote_addr,
1027 bytes = packet.data.len(),
1028 "Dropping stray punch probe/ack in FMP rx loop"
1029 );
1030 return PacketProcessAction::Done;
1031 }
1032 if packet.data.len() < COMMON_PREFIX_SIZE {
1033 return PacketProcessAction::Done; }
1035
1036 let prefix = match CommonPrefix::parse(&packet.data) {
1037 Some(p) => p,
1038 None => return PacketProcessAction::Done, };
1040 if matches!(prefix.phase, PHASE_MSG1 | PHASE_MSG2) {
1041 debug!(
1042 transport_id = %packet.transport_id,
1043 remote_addr = %packet.remote_addr,
1044 bytes = packet.data.len(),
1045 phase = prefix.phase,
1046 version = prefix.version,
1047 "FMP handshake packet dispatch"
1048 );
1049 } else {
1050 trace!(
1051 transport_id = %packet.transport_id,
1052 remote_addr = %packet.remote_addr,
1053 bytes = packet.data.len(),
1054 phase = prefix.phase,
1055 version = prefix.version,
1056 "FMP packet dispatch"
1057 );
1058 }
1059
1060 if prefix.version != FMP_VERSION {
1061 debug!(
1062 version = prefix.version,
1063 transport_id = %packet.transport_id,
1064 "Unknown FMP version, dropping"
1065 );
1066
1067 let looks_like_fmp_phase =
1075 matches!(prefix.phase, PHASE_ESTABLISHED | PHASE_MSG1 | PHASE_MSG2);
1076 if looks_like_fmp_phase
1077 && self.bootstrap_transports.contains(&packet.transport_id)
1078 && let Some(npub) = self.bootstrap_transports.peer_npub(&packet.transport_id)
1079 && let Some(handle) = self.nostr_discovery_handle()
1080 {
1081 let now_ms = Self::now_ms();
1082 let cooldown_secs = handle.protocol_mismatch_cooldown_secs();
1083 if handle.record_protocol_mismatch(npub, now_ms) {
1084 warn!(
1085 peer_npub = %npub,
1086 transport_id = %packet.transport_id,
1087 peer_version = prefix.version,
1088 our_version = FMP_VERSION,
1089 cooldown_secs,
1090 "Nostr-discovered peer speaks a different FMP version; suppressing retraversal"
1091 );
1092 }
1093 }
1094 return PacketProcessAction::Done;
1095 }
1096
1097 match prefix.phase {
1098 PHASE_ESTABLISHED => match self.try_prepare_encrypted_frame_for_worker(packet) {
1099 EncryptedFrameFastPath::Dispatch(job) => PacketProcessAction::DecryptJob { job },
1100 EncryptedFrameFastPath::Dropped => PacketProcessAction::Done,
1101 EncryptedFrameFastPath::Slow(packet) => {
1102 PacketProcessAction::EncryptedSlow { packet, timer }
1103 }
1104 },
1105 PHASE_MSG1 => PacketProcessAction::Msg1 { packet, timer },
1106 PHASE_MSG2 => PacketProcessAction::Msg2 { packet, timer },
1107 _ => {
1108 debug!(
1109 phase = prefix.phase,
1110 transport_id = %packet.transport_id,
1111 "Unknown FMP phase, dropping"
1112 );
1113 PacketProcessAction::Done
1114 }
1115 }
1116 }
1117
1118 async fn finish_packet_process(&mut self, action: PacketProcessAction) {
1119 match action {
1120 PacketProcessAction::Done => {}
1121 PacketProcessAction::DecryptJob { job } => {
1122 if let Some(workers) = self.decrypt_workers.as_ref() {
1123 workers.dispatch_job(job);
1124 }
1125 }
1126 PacketProcessAction::EncryptedSlow {
1127 packet,
1128 timer: _timer,
1129 } => {
1130 self.handle_encrypted_frame_slow(packet).await;
1131 }
1132 PacketProcessAction::Msg1 {
1133 packet,
1134 timer: _timer,
1135 } => {
1136 self.handle_msg1(packet).await;
1137 }
1138 PacketProcessAction::Msg2 {
1139 packet,
1140 timer: _timer,
1141 } => {
1142 self.handle_msg2(packet).await;
1143 }
1144 }
1145 }
1146
1147 fn flush_decrypt_job_batcher(&self, batcher: &mut DecryptJobBatcher) {
1148 if let Some(workers) = self.decrypt_workers.as_ref() {
1149 batcher.flush(workers);
1150 }
1151 }
1152}