Files
nym/nym-node/src/node/mixnet/packet_forwarding/mod.rs
T
Jędrzej Stuczyński c7eb3bdb7b moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-31 08:57:05 +00:00

166 lines
6.2 KiB
Rust

// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::routing_filter::RoutingFilter;
use futures::StreamExt;
use nym_mixnet_client::SendWithoutResponse;
use nym_mixnet_client::forwarder::{
MixForwardingReceiver, MixForwardingSender, PacketToForward, mix_forwarding_channels,
};
use nym_node_metrics::NymNodeMetrics;
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_task::ShutdownToken;
use std::io;
use tokio::time::Instant;
use tracing::{debug, error, trace, warn};
pub(crate) mod global;
pub struct PacketForwarder<C, F> {
delay_queue: NonExhaustiveDelayQueue<MixPacket>,
mixnet_client: C,
metrics: NymNodeMetrics,
routing_filter: F,
packet_sender: MixForwardingSender,
packet_receiver: MixForwardingReceiver,
}
impl<C, F> PacketForwarder<C, F> {
pub fn new(client: C, routing_filter: F, metrics: NymNodeMetrics) -> Self {
let (packet_sender, packet_receiver) = mix_forwarding_channels();
PacketForwarder {
delay_queue: NonExhaustiveDelayQueue::new(),
mixnet_client: client,
metrics,
routing_filter,
packet_sender,
packet_receiver,
}
}
pub fn sender(&self) -> MixForwardingSender {
self.packet_sender.clone()
}
fn forward_packet(&mut self, packet: MixPacket)
where
C: SendWithoutResponse,
F: RoutingFilter,
{
let next_hop = packet.next_hop_address();
if let Err(err) = self.mixnet_client.send_without_response(packet) {
if err.kind() == io::ErrorKind::WouldBlock {
// we only know for sure if we dropped a packet if our sending queue was full
// in any other case the connection might still be re-established (or created for the first time)
// and the packet might get sent, but we won't know about it
self.metrics.mixnet.egress_dropped_forward_packet(next_hop)
} else if err.kind() == io::ErrorKind::NotConnected {
// let's give the benefit of the doubt and assume we manage to establish connection
self.metrics.mixnet.egress_sent_forward_packet(next_hop)
}
} else {
self.metrics.mixnet.egress_sent_forward_packet(next_hop)
}
}
/// Upon packet being finished getting delayed, forward it to the mixnet.
fn handle_done_delaying(&mut self, packet: Expired<MixPacket>)
where
C: SendWithoutResponse,
F: RoutingFilter,
{
let delayed_packet = packet.into_inner();
self.forward_packet(delayed_packet);
}
fn handle_new_packet(&mut self, new_packet: PacketToForward)
where
C: SendWithoutResponse,
F: RoutingFilter,
{
let next_hop = new_packet.packet.next_hop();
if !self.routing_filter.should_route(next_hop.as_ref().ip()) {
debug!("dropping packet as the egress address does not belong to any known node");
self.metrics
.mixnet
.egress_dropped_forward_packet(next_hop.into());
return;
}
// in case of a zero delay packet, don't bother putting it in the delay queue,
// just forward it immediately
if let Some(instant) = new_packet.forward_delay_target {
// check if the delay has already expired, if so, don't bother putting it through
// the delay queue only to retrieve it immediately. Just forward it.
if instant.checked_duration_since(Instant::now()).is_none() {
self.forward_packet(new_packet.packet)
} else {
self.delay_queue.insert_at(new_packet.packet, instant);
}
} else {
self.forward_packet(new_packet.packet)
}
}
fn update_queue_len_metric(&self) {
self.metrics
.process
.update_forward_hop_packets_being_delayed(self.delay_queue.len());
}
fn update_channel_size_metric(&self, channel_size: usize) {
self.metrics
.process
.update_packet_forwarder_queue_size(channel_size)
}
pub async fn run(&mut self, shutdown_token: ShutdownToken)
where
C: SendWithoutResponse,
F: RoutingFilter,
{
let mut processed = 0;
trace!("starting PacketForwarder");
loop {
tokio::select! {
biased;
_ = shutdown_token.cancelled() => {
debug!("PacketForwarder: Received shutdown");
break;
}
delayed = self.delay_queue.next() => {
// SAFETY: `stream` implementation of `NonExhaustiveDelayQueue` never returns `None`
#[allow(clippy::unwrap_used)]
self.handle_done_delaying(delayed.unwrap());
}
new_packet = self.packet_receiver.next() => {
// this one is impossible to ever panic - the struct itself contains a sender
// and hence it can't happen that ALL senders are dropped
#[allow(clippy::unwrap_used)]
self.handle_new_packet(new_packet.unwrap());
let channel_len = self.packet_sender.len();
if processed % 1000 == 0 {
match channel_len {
n if n > 1000 => error!("there are currently {n} mix packets waiting to get forwarded - the node seems to be significantly overloaded!"),
n if n > 500 => warn!("there are currently {n} mix packets waiting to get forwarded - is the node overloaded?"),
n => trace!("there are currently {n} mix packets waiting to get forwarded"),
}
}
self.update_channel_size_metric(channel_len);
processed += 1;
}
}
// update the metrics on either new packet being inserted or packet being removed
self.update_queue_len_metric();
}
trace!("PacketForwarder: Exiting");
}
}