use super::*;
pub(super) fn extra_link_proof_timeout(interface: Option<&InterfaceInfo>) -> f64 {
interface
.and_then(|interface| interface.bitrate)
.filter(|bitrate| *bitrate > 0)
.map_or(0.0, |bitrate| {
(constants::MTU as f64 * 8.0) / bitrate as f64
})
}
impl TransportEngine {
fn record_invalid_announce(
&self,
announce: &AnnounceData,
interface: InterfaceId,
now: f64,
actions: &mut Vec<TransportAction>,
) {
let identity_hash = crate::hash::truncated_hash(&announce.public_key);
if !self.is_blackholed(&identity_hash, now) {
actions.push(TransportAction::ProtocolViolation { interface });
}
}
pub fn accepts_inbound_frame(&self, frame: InboundFrame<'_>) -> bool {
self.prepare_inbound_packet(frame).is_some()
}
pub fn handle_inbound(
&mut self,
frame: InboundFrame<'_>,
rng: &mut dyn Rng,
) -> Vec<TransportAction> {
self.handle_inbound_with_announce_queue(frame, rng, None)
}
pub fn handle_inbound_with_announce_queue(
&mut self,
frame: InboundFrame<'_>,
rng: &mut dyn Rng,
announce_queue: Option<&mut AnnounceVerifyQueue>,
) -> Vec<TransportAction> {
let Ok(decoded) = self.decode_inbound(PacketBytes::from(frame.raw)) else {
return Vec::new();
};
self.handle_decoded_inbound_with_announce_queue(
DecodedInboundFrame {
decoded,
iface: frame.iface,
now: frame.now,
rx: frame.rx,
},
rng,
announce_queue,
)
}
pub fn decode_inbound(
&self,
raw: PacketBytes,
) -> Result<DecodedPacket, crate::packet::PacketError> {
let flags = raw
.first()
.map(|flags| crate::packet::PacketFlags::unpack(*flags));
if !flags.is_some_and(|flags| {
matches!(
flags.packet_type,
constants::PACKET_TYPE_DATA | constants::PACKET_TYPE_PROOF
) && flags.destination_type == constants::DESTINATION_LINK
}) {
return DecodedPacket::unpack_shared(raw);
}
let mut packet = RawPacket::unpack_shared_unhashed(raw)?;
let hash_deferred = self.link_packet_hash_unread(&packet);
if !hash_deferred {
packet.hash_in_place();
}
Ok(DecodedPacket {
packet,
hash_deferred,
})
}
fn link_packet_hash_unread<B: AsRef<[u8]>>(&self, packet: &RawPacket<B>) -> bool {
self.forwards_link_packet_unhashed(packet) || self.delivers_link_resource_unhashed(packet)
}
fn delivers_link_resource_unhashed<B: AsRef<[u8]>>(&self, packet: &RawPacket<B>) -> bool {
packet.is_unhashed_link_resource()
&& self
.local_destinations
.contains_key(&packet.destination_hash)
&& !self.path_table.contains_key(&packet.destination_hash)
}
fn forwards_link_packet_unhashed<B: AsRef<[u8]>>(&self, packet: &RawPacket<B>) -> bool {
packet.flags.packet_type == constants::PACKET_TYPE_DATA
&& packet.flags.destination_type == constants::DESTINATION_LINK
&& matches!(
packet.context,
constants::CONTEXT_KEEPALIVE
| constants::CONTEXT_RESOURCE_REQ
| constants::CONTEXT_RESOURCE
| constants::CONTEXT_CACHE_REQUEST
| constants::CONTEXT_CHANNEL
)
&& (self.config.transport_enabled || self.config.identity_hash.is_some())
&& self
.link_table
.get(&packet.destination_hash)
.is_some_and(|entry| entry.validated)
&& !self
.local_destinations
.contains_key(&packet.destination_hash)
&& !self.path_table.contains_key(&packet.destination_hash)
}
pub fn handle_decoded_inbound_with_announce_queue(
&mut self,
frame: DecodedInboundFrame,
rng: &mut dyn Rng,
announce_queue: Option<&mut AnnounceVerifyQueue>,
) -> Vec<TransportAction> {
let mut actions = Vec::new();
self.handle_decoded_inbound_into(frame, rng, announce_queue, &mut actions);
actions
}
pub fn handle_decoded_inbound_into(
&mut self,
frame: DecodedInboundFrame,
rng: &mut dyn Rng,
announce_queue: Option<&mut AnnounceVerifyQueue>,
actions: &mut Vec<TransportAction>,
) {
let Some(mut ctx) = self.prepare_decoded_packet(frame) else {
return;
};
if !self.destination_filter_admits(&ctx) {
return;
}
if ctx.hash_deferred && !self.link_packet_hash_unread(&ctx.packet) {
ctx.packet.hash_in_place();
ctx.hash_deferred = false;
}
if !ctx.hash_deferred {
self.remember_inbound_packet_hash(&ctx.packet);
}
self.bridge_plain_broadcast(&ctx, actions);
self.handle_transport_forwarding(&ctx, actions);
self.handle_link_table_routing(&ctx, actions);
self.handle_inbound_announce(&ctx, rng, announce_queue, actions);
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_PROOF {
self.process_inbound_proof(&ctx, actions);
}
self.handle_inbound_local_delivery(&ctx, actions);
}
fn destination_filter_admits(&self, ctx: &InboundPacketCtx) -> bool {
if ctx.packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
return true;
}
let start = constants::KEYSIZE / 8;
let Some(name_hash) = ctx
.packet
.data()
.get(start..start + constants::NAME_HASH_LENGTH / 8)
else {
return true;
};
let mut destination_type = [0u8; constants::NAME_HASH_LENGTH / 8];
destination_type.copy_from_slice(name_hash);
let own_local_announce = ctx.from_local_client && ctx.packet.hops == 0;
self.destination_filter
.allows(&destination_type, own_local_announce)
}
pub(super) fn prepare_inbound_packet(
&self,
frame: InboundFrame<'_>,
) -> Option<InboundPacketCtx> {
let decoded = DecodedPacket::unpack(frame.raw).ok()?;
self.prepare_decoded_packet(DecodedInboundFrame {
decoded,
iface: frame.iface,
now: frame.now,
rx: frame.rx,
})
}
pub(super) fn filtered_inbound_hops<B: AsRef<[u8]>>(
&self,
packet: &RawPacket<B>,
iface: InterfaceId,
) -> Option<(u8, bool)> {
let from_local_client = self
.interfaces
.get(&iface)
.is_some_and(|i| i.is_local_client);
let mut hops = packet.hops.checked_add(1)?;
if from_local_client {
hops = hops.saturating_sub(1);
}
self.packet_filter_at_hops(packet, hops)
.then_some((hops, from_local_client))
}
pub fn accepts_decoded_packet(&self, decoded: &DecodedPacket, iface: InterfaceId) -> bool {
self.filtered_inbound_hops(&decoded.packet, iface).is_some()
}
pub(super) fn prepare_decoded_packet(
&self,
frame: DecodedInboundFrame,
) -> Option<InboundPacketCtx> {
let hash_deferred = frame.decoded.hash_deferred;
let mut packet = frame.decoded.packet;
let (hops, from_local_client) = self.filtered_inbound_hops(&packet, frame.iface)?;
packet.hops = hops;
packet.rssi = frame.rx.rssi;
packet.snr = frame.rx.snr;
let retain_original_raw = packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE;
let original_raw = retain_original_raw.then(|| packet.raw.clone());
Some(InboundPacketCtx {
packet,
hash_deferred,
original_raw,
iface: frame.iface,
now: frame.now,
from_local_client,
})
}
fn remember_inbound_packet_hash<B: AsRef<[u8]>>(&mut self, packet: &RawPacket<B>) {
let remember_hash = !(self.link_table.contains_key(&packet.destination_hash)
|| (packet.flags.packet_type == constants::PACKET_TYPE_PROOF
&& packet.context == constants::CONTEXT_LRPROOF));
if remember_hash {
self.packet_hashlist.add(packet.packet_hash);
}
}
fn bridge_plain_broadcast(&self, ctx: &InboundPacketCtx, actions: &mut Vec<TransportAction>) {
if ctx.packet.flags.destination_type != constants::DESTINATION_PLAIN
|| ctx.packet.flags.transport_type != constants::TRANSPORT_BROADCAST
|| !self.has_local_clients()
|| self
.control_destinations
.contains(&ctx.packet.destination_hash)
{
return;
}
if ctx.from_local_client {
actions.push(TransportAction::ForwardPlainBroadcast {
raw: PacketBytes::from(ctx.packet.raw.clone()),
to_local: false,
exclude: Some(ctx.iface),
});
} else {
actions.push(TransportAction::ForwardPlainBroadcast {
raw: PacketBytes::from(ctx.packet.raw.clone()),
to_local: true,
exclude: None,
});
}
}
fn handle_transport_forwarding(
&mut self,
ctx: &InboundPacketCtx,
actions: &mut Vec<TransportAction>,
) {
if !(self.config.transport_enabled || self.config.identity_hash.is_some()) {
return;
}
let for_local_client = ctx.packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
&& self
.path_table
.get(&ctx.packet.destination_hash)
.and_then(|ps| ps.primary())
.is_some_and(|entry| {
entry.hops == 0 && self.interface_is_local_client(entry.receiving_interface)
});
let transport_id = match ctx.packet.transport_id {
None if for_local_client => self.config.identity_hash,
transport_id => transport_id,
};
if transport_id.is_none() || ctx.packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
{
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
log::debug!(
"TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. not transport-addressed header={} iface={}",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
ctx.packet.flags.header_type,
ctx.iface.0
);
}
return;
}
let Some(identity_hash) = self.config.identity_hash else {
return;
};
if transport_id != Some(identity_hash) {
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
log::debug!(
"TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. transport mismatch got={:02x?} own={:02x?} iface={}",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
ctx.packet.transport_id.as_ref().map(|id| &id[..4]),
&identity_hash[..4],
ctx.iface.0
);
}
return;
}
let Some(path_entry) = self
.path_table
.get(&ctx.packet.destination_hash)
.and_then(|ps| ps.primary())
else {
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
log::debug!(
"TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. addressed to us but no path iface={}",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
ctx.iface.0
);
}
return;
};
let next_hop = path_entry.next_hop;
let remaining_hops = path_entry.hops;
let outbound_interface = path_entry.receiving_interface;
let outbound_is_local_client = self
.interfaces
.get(&outbound_interface)
.map(|info| info.is_local_client)
.unwrap_or(false);
let forwarded_remaining_hops = if outbound_is_local_client {
0
} else {
remaining_hops
};
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA {
log::debug!(
"TransportForward: DATA dest={:02x}{:02x}{:02x}{:02x}.. remaining_hops={} out_iface={} next_hop={:02x?}",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
remaining_hops,
outbound_interface.0,
&next_hop[..4]
);
}
let mut new_raw = forward_transport_packet(
&ctx.packet,
next_hop,
forwarded_remaining_hops,
outbound_interface,
);
if self.config.local_hops_delta != 0
&& ctx.from_local_client
&& !outbound_is_local_client
&& ctx.packet.hops == 0
&& ctx.packet.flags.destination_type != constants::DESTINATION_PLAIN
&& ctx.packet.flags.destination_type != constants::DESTINATION_GROUP
&& new_raw.len() > 1
{
new_raw[1] = self.config.local_hops_delta;
}
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST {
clamp_forwarded_link_request_mtu(
&mut new_raw,
ctx.packet.data().len() == constants::LINK_ECPUBSIZE + constants::LINK_MTU_SIZE,
self.interfaces.get(&ctx.iface).map(|info| info.mtu),
self.interfaces
.get(&outbound_interface)
.map(|info| info.mtu),
);
let extra_proof_timeout =
extra_link_proof_timeout(self.interfaces.get(&outbound_interface));
let proof_timeout = ctx.now
+ constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP * (remaining_hops.max(1) as f64)
+ extra_proof_timeout;
let (link_id, link_entry) = create_link_entry(
&ctx.packet,
next_hop,
outbound_interface,
remaining_hops,
ctx.iface,
ctx.now,
proof_timeout,
);
self.link_table.insert(link_id, link_entry);
actions.push(TransportAction::LinkRequestReceived {
link_id,
destination_hash: ctx.packet.destination_hash,
receiving_interface: ctx.iface,
});
} else {
let (trunc_hash, reverse_entry) =
create_reverse_entry(&ctx.packet, outbound_interface, ctx.iface, ctx.now);
self.reverse_table.insert(trunc_hash, reverse_entry);
}
actions.push(TransportAction::SendOnInterface {
interface: outbound_interface,
raw: new_raw.into(),
});
if let Some(entry) = self
.path_table
.get_mut(&ctx.packet.destination_hash)
.and_then(|ps| ps.primary_mut())
{
entry.timestamp = ctx.now;
}
}
fn handle_link_table_routing(
&mut self,
ctx: &InboundPacketCtx,
actions: &mut Vec<TransportAction>,
) {
if !self.config.transport_enabled && self.config.identity_hash.is_none() {
return;
}
if ctx.packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
|| ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
|| ctx.packet.context == constants::CONTEXT_LRPROOF
{
return;
}
let Some(link_entry) = self.link_table.get(&ctx.packet.destination_hash).cloned() else {
return;
};
if !link_entry.validated {
return;
}
let instance_local_link = self.interface_is_local_client(link_entry.next_hop_interface)
&& self.interface_is_local_client(link_entry.received_interface);
let Some((outbound_iface, new_raw)) = super::inbound::route_via_link_table_shared(
&ctx.packet,
&link_entry,
ctx.iface,
LocalHopRewrite {
local_hops_delta: self.config.local_hops_delta,
from_local_client: ctx.from_local_client,
skip_local_hops_delta: instance_local_link,
},
) else {
return;
};
if !ctx.hash_deferred {
self.packet_hashlist.add(ctx.packet.packet_hash);
}
actions.push(TransportAction::SendOnInterface {
interface: outbound_iface,
raw: new_raw,
});
if let Some(entry) = self.link_table.get_mut(&ctx.packet.destination_hash) {
entry.timestamp = ctx.now;
}
}
fn handle_inbound_announce(
&mut self,
ctx: &InboundPacketCtx,
rng: &mut dyn Rng,
announce_queue: Option<&mut AnnounceVerifyQueue>,
actions: &mut Vec<TransportAction>,
) {
if ctx.packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
return;
}
if let Some(queue) = announce_queue {
self.try_enqueue_announce(ctx, rng, queue, actions);
} else {
let original_raw = ctx
.original_raw
.as_deref()
.expect("announce packets retain original raw bytes");
self.process_inbound_announce(
&ctx.packet,
original_raw,
ctx.iface,
ctx.now,
rng,
actions,
);
}
}
fn handle_inbound_local_delivery(
&self,
ctx: &InboundPacketCtx,
actions: &mut Vec<TransportAction>,
) {
if (ctx.packet.flags.packet_type == constants::PACKET_TYPE_LINKREQUEST
|| ctx.packet.flags.packet_type == constants::PACKET_TYPE_DATA)
&& self
.local_destinations
.contains_key(&ctx.packet.destination_hash)
{
let delivery_raw =
if ctx.packet.context == constants::CONTEXT_LRRTT && ctx.packet.raw.len() >= 2 {
let mut raw = ctx.packet.raw.to_vec();
raw[1] = ctx.packet.hops;
PacketBytes::from(raw)
} else {
ctx.packet.raw.clone()
};
actions.push(TransportAction::DeliverLocal {
destination_hash: ctx.packet.destination_hash,
raw: delivery_raw,
packet_hash: ctx.packet.packet_hash,
receiving_interface: ctx.iface,
});
}
}
fn process_inbound_announce(
&mut self,
packet: &RawPacket<PacketBytes>,
original_raw: &[u8],
iface: InterfaceId,
now: f64,
rng: &mut dyn Rng,
actions: &mut Vec<TransportAction>,
) {
if packet.flags.destination_type != constants::DESTINATION_SINGLE {
return;
}
let has_ratchet = packet.flags.context_flag == constants::FLAG_SET;
let announce = match AnnounceData::unpack(packet.data(), has_ratchet) {
Ok(a) => a,
Err(_) => {
actions.push(TransportAction::ProtocolViolation { interface: iface });
return;
}
};
if self.should_hold_announce(packet, original_raw, iface, now) {
return;
}
let sig_cache_key = Self::announce_sig_cache_key(&packet.destination_hash, &announce);
let validated = if self.announce_sig_cache.contains(&sig_cache_key) {
announce.to_validated_with_cached_signature(&packet.destination_hash)
} else {
announce.validate(&packet.destination_hash).inspect(|_| {
self.announce_sig_cache.insert(sig_cache_key, now);
})
};
let validated = match validated {
Ok(v) => v,
Err(_) => {
self.record_invalid_announce(&announce, iface, now, actions);
return;
}
};
let received_from = self.announce_received_from(packet, now);
let random_blob = match extract_random_blob(packet.data()) {
Some(b) => b,
None => {
actions.push(TransportAction::ProtocolViolation { interface: iface });
return;
}
};
let announce_emitted = timebase_from_random_blob(&random_blob);
self.process_verified_announce(
VerifiedAnnounceCtx {
packet,
original_raw,
iface,
now,
validated,
received_from,
random_blob,
announce_emitted,
},
rng,
actions,
);
}
fn announce_raw_for_local_clients(&self, packet: &RawPacket<PacketBytes>) -> PacketBytes {
let Some(identity_hash) = self.config.identity_hash else {
return packet.raw.clone();
};
if packet.raw.len() < 2 {
return packet.raw.clone();
}
let payload_start = if packet.flags.header_type == constants::HEADER_2 {
18usize
} else {
2usize
};
if packet.raw.len() < payload_start {
return packet.raw.clone();
}
let flags = (constants::HEADER_2 << 6)
| (constants::TRANSPORT_TRANSPORT << 4)
| (packet.raw[0] & 0x0F);
let mut raw = Vec::with_capacity(18 + packet.raw.len() - payload_start);
raw.push(flags);
raw.push(packet.hops);
raw.extend_from_slice(&identity_hash);
raw.extend_from_slice(&packet.raw[payload_start..]);
PacketBytes::from(raw)
}
pub(super) fn announce_sig_cache_key(
destination_hash: &[u8; 16],
announce: &AnnounceData,
) -> [u8; 32] {
announce.signature_cache_key(destination_hash)
}
fn announce_received_from<B: AsRef<[u8]>>(
&mut self,
packet: &RawPacket<B>,
now: f64,
) -> [u8; 16] {
if let Some(transport_id) = packet.transport_id {
if self.config.transport_enabled {
if let Some(announce_entry) = self.announce_table.get_mut(&packet.destination_hash)
{
if packet.hops.checked_sub(1) == Some(announce_entry.hops) {
announce_entry.local_rebroadcasts += 1;
if announce_entry.retries > 0
&& announce_entry.local_rebroadcasts
>= constants::LOCAL_REBROADCASTS_MAX
{
self.announce_table.remove(&packet.destination_hash);
}
}
if let Some(announce_entry) = self.announce_table.get(&packet.destination_hash)
{
if packet.hops.checked_sub(1) == Some(announce_entry.hops + 1)
&& announce_entry.retries > 0
&& now < announce_entry.retransmit_timeout
{
self.announce_table.remove(&packet.destination_hash);
}
}
}
}
transport_id
} else {
packet.destination_hash
}
}
fn should_hold_announce<B: AsRef<[u8]>>(
&mut self,
packet: &RawPacket<B>,
original_raw: &[u8],
iface: InterfaceId,
now: f64,
) -> bool {
if self.has_path(&packet.destination_hash) {
return false;
}
if self
.discovery_path_requests
.contains_key(&packet.destination_hash)
{
return false;
}
let Some(info) = self.interfaces.get(&iface) else {
return false;
};
if packet.context == constants::CONTEXT_PATH_RESPONSE
|| !self.ingress_control.should_ingress_limit(
iface,
&info.ingress_control,
info.ia_freq,
info.started,
now,
)
{
return false;
}
self.ingress_control.hold_announce(
iface,
&info.ingress_control,
packet.destination_hash,
ingress_control::HeldAnnounce {
raw: original_raw.to_vec(),
hops: packet.hops,
receiving_interface: iface,
rx: RxMetadata {
rssi: packet.rssi,
snr: packet.snr,
},
timestamp: now,
},
);
true
}
fn try_enqueue_announce(
&mut self,
ctx: &InboundPacketCtx,
rng: &mut dyn Rng,
announce_queue: &mut AnnounceVerifyQueue,
actions: &mut Vec<TransportAction>,
) {
if ctx.packet.flags.destination_type != constants::DESTINATION_SINGLE {
return;
}
let has_ratchet = ctx.packet.flags.context_flag == constants::FLAG_SET;
let announce = match AnnounceData::unpack(ctx.packet.data(), has_ratchet) {
Ok(a) => a,
Err(_) => {
actions.push(TransportAction::ProtocolViolation {
interface: ctx.iface,
});
return;
}
};
let received_from = self.announce_received_from(&ctx.packet, ctx.now);
if self
.local_destinations
.contains_key(&ctx.packet.destination_hash)
{
log::trace!(target: crate::logging::PATHING_LOG_TARGET,
"Announce:skipping local destination {:02x}{:02x}{:02x}{:02x}..",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
);
return;
}
let original_raw = ctx
.original_raw
.as_deref()
.expect("announce packets retain original raw bytes");
if self.should_hold_announce(&ctx.packet, original_raw, ctx.iface, ctx.now) {
return;
}
let sig_cache_key = Self::announce_sig_cache_key(&ctx.packet.destination_hash, &announce);
if self.announce_sig_cache.contains(&sig_cache_key) {
let Ok(validated) =
announce.to_validated_with_cached_signature(&ctx.packet.destination_hash)
else {
self.record_invalid_announce(&announce, ctx.iface, ctx.now, actions);
return;
};
let random_blob = match extract_random_blob(ctx.packet.data()) {
Some(b) => b,
None => return,
};
let announce_emitted = timebase_from_random_blob(&random_blob);
self.process_verified_announce(
VerifiedAnnounceCtx {
packet: &ctx.packet,
original_raw,
iface: ctx.iface,
now: ctx.now,
validated,
received_from,
random_blob,
announce_emitted,
},
rng,
actions,
);
return;
}
if ctx.packet.context == constants::CONTEXT_PATH_RESPONSE {
let Ok(validated) = announce.validate(&ctx.packet.destination_hash) else {
self.record_invalid_announce(&announce, ctx.iface, ctx.now, actions);
return;
};
self.announce_sig_cache.insert(sig_cache_key, ctx.now);
let random_blob = match extract_random_blob(ctx.packet.data()) {
Some(b) => b,
None => return,
};
let announce_emitted = timebase_from_random_blob(&random_blob);
self.process_verified_announce(
VerifiedAnnounceCtx {
packet: &ctx.packet,
original_raw,
iface: ctx.iface,
now: ctx.now,
validated,
received_from,
random_blob,
announce_emitted,
},
rng,
actions,
);
return;
}
let random_blob = match extract_random_blob(ctx.packet.data()) {
Some(b) => b,
None => {
actions.push(TransportAction::ProtocolViolation {
interface: ctx.iface,
});
return;
}
};
let announce_emitted = timebase_from_random_blob(&random_blob);
let key = AnnounceVerifyKey {
destination_hash: ctx.packet.destination_hash,
random_blob,
received_from,
};
let pending = PendingAnnounce {
original_raw: original_raw.to_vec(),
packet: ctx.packet.clone(),
interface: ctx.iface,
received_from,
queued_at: ctx.now,
best_hops: ctx.packet.hops,
emission_ts: announce_emitted,
random_blob,
};
let _ = announce_queue.enqueue(key, pending);
}
pub fn complete_verified_announce(
&mut self,
pending: PendingAnnounce,
validated: crate::announce::ValidatedAnnounce,
sig_cache_key: [u8; 32],
now: f64,
rng: &mut dyn Rng,
) -> Vec<TransportAction> {
self.announce_sig_cache.insert(sig_cache_key, now);
let mut actions = Vec::new();
self.process_verified_announce(
VerifiedAnnounceCtx {
packet: &pending.packet,
original_raw: &pending.original_raw,
iface: pending.interface,
now,
validated,
received_from: pending.received_from,
random_blob: pending.random_blob,
announce_emitted: pending.emission_ts,
},
rng,
&mut actions,
);
actions
}
pub fn clear_failed_verified_announce(&mut self, _sig_cache_key: [u8; 32], _now: f64) {}
fn process_verified_announce(
&mut self,
mut ctx: VerifiedAnnounceCtx<'_>,
rng: &mut dyn Rng,
actions: &mut Vec<TransportAction>,
) {
if self.is_blackholed(&ctx.validated.identity_hash, ctx.now) {
return;
}
if ctx.packet.hops > constants::PATHFINDER_M {
return;
}
let required_value = self
.interfaces
.get(&ctx.iface)
.map(|info| info.stamp_costs.destination_cost(&self.stamp_requirements))
.unwrap_or(self.stamp_requirements.destination_cost);
let stamp_status = ctx
.validated
.apply_stamp(&ctx.packet.destination_hash, required_value);
if self.stamp_requirements.enforced
&& stamp_status != crate::announce::AnnounceStampStatus::Valid
{
return;
}
let existing_set = self.path_table.get(&ctx.packet.destination_hash);
let was_unknown_destination = existing_set.is_none_or(|ps| ps.is_empty());
if was_unknown_destination {
self.path_states.remove(&ctx.packet.destination_hash);
}
let is_unresponsive = self.path_is_unresponsive(&ctx.packet.destination_hash);
let current_gravity = existing_set
.and_then(|path_set| path_set.primary())
.and_then(|path| self.interfaces.get(&path.receiving_interface))
.map(|interface| interface.gravity);
let announce_gravity = self
.interfaces
.get(&ctx.iface)
.map(|interface| interface.gravity);
let higher_gravity_replacement = existing_set.is_some_and(|path_set| {
pathfinder::is_higher_gravity_replacement(
path_set,
ctx.packet.hops,
ctx.announce_emitted,
ctx.now,
current_gravity,
announce_gravity,
)
});
let mp_decision = pathfinder::decide_announce_multipath_with_gravity(
existing_set,
ctx.packet.hops,
ctx.announce_emitted,
&ctx.random_blob,
&ctx.received_from,
is_unresponsive,
ctx.now,
self.config.prefer_shorter_path,
current_gravity,
announce_gravity,
);
if mp_decision == MultiPathDecision::Reject {
log::trace!(target: crate::logging::PATHING_LOG_TARGET,
"Announce:path decision REJECT for dest={:02x}{:02x}{:02x}{:02x}..",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
);
return;
}
if higher_gravity_replacement {
log::log!(
target: crate::logging::PATHING_LOG_TARGET,
crate::logging::GRAVITY_UPDATE_LOG_LEVEL,
"Replacing path table entry for {:02x}{:02x}{:02x}{:02x}.. due to higher gravity ({:?}->{:?})",
ctx.packet.destination_hash[0],
ctx.packet.destination_hash[1],
ctx.packet.destination_hash[2],
ctx.packet.destination_hash[3],
current_gravity,
announce_gravity,
);
}
let rate_blocked = if ctx.packet.context != constants::CONTEXT_PATH_RESPONSE {
if let Some(iface_info) = self.interfaces.get(&ctx.iface) {
self.rate_limiter.check_and_update(
&ctx.packet.destination_hash,
ctx.now,
iface_info.announce_rate_target,
iface_info.announce_rate_grace,
iface_info.announce_rate_penalty,
)
} else {
false
}
} else {
false
};
let interface_mode = self
.interfaces
.get(&ctx.iface)
.map(|i| i.mode)
.unwrap_or(constants::MODE_FULL);
let expires = compute_path_expires(ctx.now, interface_mode);
let existing_blobs = self
.path_table
.get(&ctx.packet.destination_hash)
.and_then(|ps| ps.find_by_next_hop(&ctx.received_from))
.map(|e| e.random_blobs.clone())
.unwrap_or_default();
let mut rng_bytes = [0u8; 8];
rng.fill_bytes(&mut rng_bytes);
let rng_value = (u64::from_le_bytes(rng_bytes) as f64) / (u64::MAX as f64);
let is_path_response = ctx.packet.context == constants::CONTEXT_PATH_RESPONSE;
let (path_entry, announce_entry) = announce_proc::process_validated_announce(
ctx.packet.destination_hash,
ctx.packet.hops,
ctx.packet.data(),
&ctx.packet.raw,
ctx.packet.packet_hash,
ctx.packet.flags.context_flag,
ctx.received_from,
ctx.iface,
ctx.now,
existing_blobs,
ctx.random_blob,
expires,
rng_value,
self.config.transport_enabled,
self.serves_local_client(ctx.iface),
is_path_response,
rate_blocked,
Some(ctx.original_raw.to_vec()),
);
actions.push(TransportAction::CacheAnnounce {
packet_hash: ctx.packet.packet_hash,
raw: ctx.original_raw.to_vec().into(),
});
match mp_decision {
MultiPathDecision::ReplacePrimary => self.upsert_primary_path_destination(
ctx.packet.destination_hash,
path_entry,
ctx.now,
),
MultiPathDecision::AddAlternative => {
self.upsert_path_destination(ctx.packet.destination_hash, path_entry, ctx.now)
}
MultiPathDecision::Reject => unreachable!("rejected decisions returned above"),
}
if let Some(tunnel_id) = self.interfaces.get(&ctx.iface).and_then(|i| i.tunnel_id) {
let blobs = self
.path_table
.get(&ctx.packet.destination_hash)
.and_then(|ps| ps.find_by_next_hop(&ctx.received_from))
.map(|e| e.random_blobs.clone())
.unwrap_or_default();
self.tunnel_table.store_tunnel_path(
&tunnel_id,
ctx.packet.destination_hash,
tunnel::TunnelPath {
timestamp: ctx.now,
received_from: ctx.received_from,
hops: ctx.packet.hops,
expires,
random_blobs: blobs,
packet_hash: ctx.packet.packet_hash,
},
ctx.now,
self.config.destination_timeout_secs,
self.config.max_tunnel_destinations_total,
);
}
self.path_states.remove(&ctx.packet.destination_hash);
if let Some(ann) = announce_entry {
self.insert_announce_entry(ctx.packet.destination_hash, ann, ctx.now);
} else if is_path_response && self.serves_local_client(ctx.iface) {
if let Some((requesting_interface, _)) = self
.pending_local_path_requests
.remove(&ctx.packet.destination_hash)
{
let entry = AnnounceEntry {
timestamp: ctx.now,
retransmit_timeout: ctx.now,
retries: constants::PATHFINDER_R,
received_from: ctx.received_from,
hops: ctx.packet.hops,
packet_raw: ctx.packet.raw.to_vec(),
packet_data: ctx.packet.data().to_vec(),
destination_hash: ctx.packet.destination_hash,
context_flag: ctx.packet.flags.context_flag,
local_rebroadcasts: 0,
block_rebroadcasts: true,
attached_interface: Some(requesting_interface),
};
self.insert_announce_entry(ctx.packet.destination_hash, entry, ctx.now);
}
}
actions.push(TransportAction::AnnounceReceived(alloc::boxed::Box::new(
types::ReceivedAnnounce {
destination_hash: ctx.packet.destination_hash,
identity_hash: ctx.validated.identity_hash,
public_key: ctx.validated.public_key,
name_hash: ctx.validated.name_hash,
random_hash: ctx.validated.random_hash,
ratchet: ctx.validated.ratchet,
app_data: ctx.validated.app_data,
stamp: ctx.validated.stamp,
stamp_value: ctx.validated.stamp_value,
lr_cost: ctx.validated.lr_cost,
hops: ctx.packet.hops,
receiving_interface: ctx.iface,
rx: RxMetadata {
rssi: ctx.packet.rssi,
snr: ctx.packet.snr,
},
},
)));
actions.push(TransportAction::PathUpdated {
destination_hash: ctx.packet.destination_hash,
hops: ctx.packet.hops,
next_hop: ctx.received_from,
interface: ctx.iface,
});
if self.has_local_clients() {
actions.push(TransportAction::ForwardToLocalClients {
raw: self.announce_raw_for_local_clients(ctx.packet),
exclude: Some(ctx.iface),
});
}
if let Some(requesting_interfaces) =
self.discovery_path_requests_waiting(&ctx.packet.destination_hash)
{
let entry = AnnounceEntry {
timestamp: ctx.now,
retransmit_timeout: ctx.now,
retries: constants::PATHFINDER_R,
received_from: ctx.received_from,
hops: ctx.packet.hops,
packet_raw: ctx.packet.raw.to_vec(),
packet_data: ctx.packet.data().to_vec(),
destination_hash: ctx.packet.destination_hash,
context_flag: ctx.packet.flags.context_flag,
local_rebroadcasts: 0,
block_rebroadcasts: true,
attached_interface: requesting_interfaces.first().copied(),
};
if let Some(identity_hash) = self.config.identity_hash {
let raw = announce_proc::build_retransmit_announce(&entry, &identity_hash);
for interface in requesting_interfaces.iter().skip(1) {
actions.push(TransportAction::SendOnInterface {
interface: *interface,
raw: raw.clone().into(),
});
}
}
self.insert_announce_entry(ctx.packet.destination_hash, entry, ctx.now);
}
}
pub fn announce_sig_cache_contains(&self, sig_cache_key: &[u8; 32]) -> bool {
self.announce_sig_cache.contains(sig_cache_key)
}
pub(super) fn discovery_path_requests_waiting(
&mut self,
dest_hash: &[u8; 16],
) -> Option<Vec<InterfaceId>> {
let request = self
.discovery_path_requests
.remove(dest_hash)
.map(|req| req.requesting_interfaces);
self.discovery_path_request_deadlines.remove(dest_hash);
request
}
fn process_inbound_proof(
&mut self,
ctx: &InboundPacketCtx,
actions: &mut Vec<TransportAction>,
) {
let packet = &ctx.packet;
if packet.context == constants::CONTEXT_LRPROOF {
let for_local_client_link =
self.link_table
.get(&packet.destination_hash)
.is_some_and(|entry| {
self.interface_is_local_client(entry.next_hop_interface)
|| self.interface_is_local_client(entry.received_interface)
});
if (self.config.transport_enabled || ctx.from_local_client || for_local_client_link)
&& self.link_table.contains_key(&packet.destination_hash)
{
let link_entry = self.link_table.get(&packet.destination_hash).cloned();
if let Some(entry) = link_entry {
let instance_local_link = self
.interface_is_local_client(entry.next_hop_interface)
&& self.interface_is_local_client(entry.received_interface);
if let Some((outbound_interface, new_raw)) = route_via_link_table(
packet,
&entry,
ctx.iface,
LocalHopRewrite {
local_hops_delta: self.config.local_hops_delta,
from_local_client: ctx.from_local_client,
skip_local_hops_delta: instance_local_link,
},
) {
if let Some(le) = self.link_table.get_mut(&packet.destination_hash) {
le.validated = true;
}
actions.push(TransportAction::LinkEstablished {
link_id: packet.destination_hash,
interface: outbound_interface,
});
actions.push(TransportAction::SendOnInterface {
interface: outbound_interface,
raw: new_raw.into(),
});
} else if link_route_hops_match(packet.hops, &entry, ctx.iface) {
log::debug!(
"Link request proof received on wrong interface {}, not transporting it (expected {} or {})",
ctx.iface.0,
entry.next_hop_interface.0,
entry.received_interface.0,
);
} else {
log::debug!("{}", lrproof_hop_mismatch_diagnostic(packet.hops, &entry));
}
}
} else {
let mut delivery_raw = packet.raw.to_vec();
if delivery_raw.len() >= 2 {
delivery_raw[1] = packet.hops;
}
actions.push(TransportAction::DeliverLocal {
destination_hash: packet.destination_hash,
raw: PacketBytes::from(delivery_raw),
packet_hash: packet.packet_hash,
receiving_interface: ctx.iface,
});
}
} else {
let proof_for_local_client = self
.reverse_table
.get(&packet.destination_hash)
.is_some_and(|entry| self.interface_is_local_client(entry.receiving_interface));
if self.config.transport_enabled || ctx.from_local_client || proof_for_local_client {
if let Some(reverse_entry) = self.reverse_table.remove(&packet.destination_hash) {
if let Some(action) = route_proof_via_reverse(
packet,
&reverse_entry,
ctx.iface,
LocalHopRewrite {
local_hops_delta: self.config.local_hops_delta,
from_local_client: ctx.from_local_client,
skip_local_hops_delta: proof_for_local_client,
},
) {
actions.push(action);
}
}
}
actions.push(TransportAction::DeliverLocal {
destination_hash: packet.destination_hash,
raw: packet.raw.clone(),
packet_hash: packet.packet_hash,
receiving_interface: ctx.iface,
});
}
}
}