v1.0.6: Fix link establishment through boundary node
Bug fixes: - Fix path_request_handler hops: use DestinationEntry._hops instead of stale cached announce_packet.hops(). The cached packet retains its original wire hops (pre-increment), but Python Transport.py explicitly overwrites packet.hops from path_table after retrieval (line 2736). This caused PATH_RESPONSE to report fewer hops than actual, making the sender's expected_hops too low, which caused LRPROOF hop-count validation to silently fail. (ROOT CAUSE of link timeout) - Fix std::map::insert() no-op: erase before insert at 3 locations in _announce_table. Unlike Python dict assignment, C++ map::insert() does not overwrite existing keys. This prevented announce table updates from taking effect. (Caused PATH-RESP delivery failure) - Defer packet hash filtering for link table entries and LRPROOF packets. Matching Python Transport behavior (line 1544), packets belonging to active links are not added to the filter hashlist until link transport processing determines it is our turn to forward them. Prevents premature filtering that breaks link transport. - Pass DestinationEntry and LinkEntry by reference instead of by value to avoid stale copies and unnecessary allocations. - Add link_table check before requesting paths for link_id destinations. Link data packets are handled by link transport, not standard path lookup, so spurious path requests are avoided. - Add culling for _held_announces (60s timeout, cap 32) and _boundary_local_addresses to prevent unbounded memory growth. - TcpInterface: detect and log partial writes.
This commit is contained in:
@@ -251,6 +251,8 @@ protected:
|
|||||||
size_t written = _clients[i].client.write(frame_buf, flen);
|
size_t written = _clients[i].client.write(frame_buf, flen);
|
||||||
if (written == 0) {
|
if (written == 0) {
|
||||||
_cleanup_client(i, "write failed");
|
_cleanup_client(i, "write failed");
|
||||||
|
} else if (written < flen) {
|
||||||
|
Serial.printf("[TcpIF] PARTIAL write to client %d: %u/%u bytes\r\n", i, (unsigned)written, (unsigned)flen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
|
|
||||||
// Process announces needing retransmission
|
// Process announces needing retransmission
|
||||||
if (OS::time() > (_announces_last_checked + _announces_check_interval)) {
|
if (OS::time() > (_announces_last_checked + _announces_check_interval)) {
|
||||||
|
DEBUG("DIAG: ANNOUNCE-TBL size=" + std::to_string(_announce_table.size()));
|
||||||
//p for destination_hash in Transport.announce_table:
|
//p for destination_hash in Transport.announce_table:
|
||||||
for (auto& [destination_hash, announce_entry] : _announce_table) {
|
for (auto& [destination_hash, announce_entry] : _announce_table) {
|
||||||
//for (auto& pair : _announce_table) {
|
//for (auto& pair : _announce_table) {
|
||||||
@@ -349,6 +350,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
// auto& announce_entry = pair.second;
|
// auto& announce_entry = pair.second;
|
||||||
//TRACE("[0] announce entry data size: " + std::to_string(announce_entry._packet.data().size()));
|
//TRACE("[0] announce entry data size: " + std::to_string(announce_entry._packet.data().size()));
|
||||||
//p announce_entry = Transport.announce_table[destination_hash]
|
//p announce_entry = Transport.announce_table[destination_hash]
|
||||||
|
DEBUG("DIAG: ANNOUNCE-ENTRY dest=" + destination_hash.toHex().substr(0,8) + " retries=" + std::to_string(announce_entry._retries) + " block=" + std::to_string(announce_entry._block_rebroadcasts) + " timeout_in=" + std::to_string(announce_entry._retransmit_timeout - OS::time()));
|
||||||
if (announce_entry._retries > 0 && announce_entry._retries >= Type::Transport::LOCAL_REBROADCASTS_MAX) {
|
if (announce_entry._retries > 0 && announce_entry._retries >= Type::Transport::LOCAL_REBROADCASTS_MAX) {
|
||||||
TRACE("Completed announce processing for " + destination_hash.toHex() + ", local rebroadcast limit reached");
|
TRACE("Completed announce processing for " + destination_hash.toHex() + ", local rebroadcast limit reached");
|
||||||
// CBA OK to modify collection here since we're immediately exiting iteration
|
// CBA OK to modify collection here since we're immediately exiting iteration
|
||||||
@@ -356,6 +358,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (announce_entry._retries > Type::Transport::PATHFINDER_R) {
|
else if (announce_entry._retries > Type::Transport::PATHFINDER_R) {
|
||||||
|
DEBUG("DIAG: ANNOUNCE-CULL dest=" + destination_hash.toHex().substr(0,8) + " retries=" + std::to_string(announce_entry._retries) + " reason=retry_limit");
|
||||||
TRACE("Completed announce processing for " + destination_hash.toHex() + ", retry limit reached");
|
TRACE("Completed announce processing for " + destination_hash.toHex() + ", retry limit reached");
|
||||||
// CBA OK to modify collection here since we're immediately exiting iteration
|
// CBA OK to modify collection here since we're immediately exiting iteration
|
||||||
_announce_table.erase(destination_hash);
|
_announce_table.erase(destination_hash);
|
||||||
@@ -403,6 +406,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
new_packet.hops(announce_entry._hops);
|
new_packet.hops(announce_entry._hops);
|
||||||
if (announce_entry._block_rebroadcasts) {
|
if (announce_entry._block_rebroadcasts) {
|
||||||
DEBUG("Rebroadcasting announce as path response for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
|
DEBUG("Rebroadcasting announce as path response for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
|
||||||
|
DEBUG("DIAG: SENDING PATH-RESP announce for " + announce_destination.hash().toHex().substr(0,8) + " hops=" + std::to_string(new_packet.hops()) + " attached=" + (announce_entry._attached_interface ? announce_entry._attached_interface.toString() : "NONE"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG("Rebroadcasting announce for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
|
DEBUG("Rebroadcasting announce for " + announce_destination.hash().toHex() + " with hop count " + std::to_string(new_packet.hops()));
|
||||||
@@ -440,6 +444,25 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
_announces_last_checked = OS::time();
|
_announces_last_checked = OS::time();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cull held announces that are older than 60 seconds or if map exceeds cap
|
||||||
|
{
|
||||||
|
const double held_timeout = 60.0;
|
||||||
|
const uint16_t held_maxsize = 32;
|
||||||
|
auto iter = _held_announces.begin();
|
||||||
|
while (iter != _held_announces.end()) {
|
||||||
|
if (OS::time() > ((*iter).second._timestamp + held_timeout)) {
|
||||||
|
DEBUG("Culling expired held announce for " + (*iter).first.toHex());
|
||||||
|
iter = _held_announces.erase(iter);
|
||||||
|
} else {
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (_held_announces.size() > held_maxsize) {
|
||||||
|
DEBUG("Culling oldest held announce (cap " + std::to_string(held_maxsize) + ")");
|
||||||
|
_held_announces.erase(_held_announces.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cull the packet hashlist if it has reached its max size
|
// Cull the packet hashlist if it has reached its max size
|
||||||
if (_packet_hashlist.size() > _hashlist_maxsize) {
|
if (_packet_hashlist.size() > _hashlist_maxsize) {
|
||||||
std::set<Bytes>::iterator iter = _packet_hashlist.begin();
|
std::set<Bytes>::iterator iter = _packet_hashlist.begin();
|
||||||
@@ -454,6 +477,13 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
std::advance(iter, _boundary_mentioned_addresses.size() - _boundary_maxsize);
|
std::advance(iter, _boundary_mentioned_addresses.size() - _boundary_maxsize);
|
||||||
_boundary_mentioned_addresses.erase(_boundary_mentioned_addresses.begin(), iter);
|
_boundary_mentioned_addresses.erase(_boundary_mentioned_addresses.begin(), iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cull the boundary local addresses if it has reached its max size
|
||||||
|
if (_boundary_local_addresses.size() > _boundary_maxsize) {
|
||||||
|
std::set<Bytes>::iterator iter = _boundary_local_addresses.begin();
|
||||||
|
std::advance(iter, _boundary_local_addresses.size() - _boundary_maxsize);
|
||||||
|
_boundary_local_addresses.erase(_boundary_local_addresses.begin(), iter);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Cull the path request tags list if it has reached its max size
|
// Cull the path request tags list if it has reached its max size
|
||||||
@@ -680,6 +710,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
|
|
||||||
// CBA send announce retransmission packets
|
// CBA send announce retransmission packets
|
||||||
for (auto& packet : outgoing) {
|
for (auto& packet : outgoing) {
|
||||||
|
DEBUG("DIAG: OUTGOING announce dest=" + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()) + " attached=" + (packet.attached_interface() ? packet.attached_interface().toString() : "NONE"));
|
||||||
packet.send();
|
packet.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,7 +808,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.destination().type() != Type::Destination::PLAIN && packet.destination().type() != Type::Destination::GROUP && _destination_table.find(packet.destination_hash()) != _destination_table.end()) {
|
if (packet.packet_type() != Type::Packet::ANNOUNCE && packet.destination().type() != Type::Destination::PLAIN && packet.destination().type() != Type::Destination::GROUP && _destination_table.find(packet.destination_hash()) != _destination_table.end()) {
|
||||||
TRACE("Transport::outbound: Path to destination is known");
|
TRACE("Transport::outbound: Path to destination is known");
|
||||||
//outbound_interface = Transport.destination_table[packet.destination_hash][5]
|
//outbound_interface = Transport.destination_table[packet.destination_hash][5]
|
||||||
DestinationEntry destination_entry = (*_destination_table.find(packet.destination_hash())).second;
|
DestinationEntry& destination_entry = (*_destination_table.find(packet.destination_hash())).second;
|
||||||
Interface outbound_interface = destination_entry.receiving_interface();
|
Interface outbound_interface = destination_entry.receiving_interface();
|
||||||
|
|
||||||
// If there's more than one hop to the destination, and we know
|
// If there's more than one hop to the destination, and we know
|
||||||
@@ -1089,6 +1120,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
|
|
||||||
if (should_transmit) {
|
if (should_transmit) {
|
||||||
TRACE("Transport::outbound: Packet transmission allowed");
|
TRACE("Transport::outbound: Packet transmission allowed");
|
||||||
|
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
|
||||||
|
DEBUG("DIAG: TX-OUT announce dest=" + packet.destination_hash().toHex().substr(0,8) + " on " + interface.toString());
|
||||||
|
}
|
||||||
if (!stored_hash) {
|
if (!stored_hash) {
|
||||||
// CBA ACCUMULATES
|
// CBA ACCUMULATES
|
||||||
_packet_hashlist.insert(packet.packet_hash());
|
_packet_hashlist.insert(packet.packet_hash());
|
||||||
@@ -1111,6 +1145,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
TRACE("Transport::outbound: Packet transmission refused");
|
TRACE("Transport::outbound: Packet transmission refused");
|
||||||
|
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
|
||||||
|
DEBUG("DIAG: TX-REFUSED announce dest=" + packet.destination_hash().toHex().substr(0,8) + " refused on " + interface.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1216,7 +1253,8 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG("Filtered packet with hash " + packet.packet_hash().toHex());
|
DEBUG("FILTERED duplicate packet " + packet.packet_hash().toHex().substr(0,8) + " dest=" + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()));
|
||||||
|
DEBUG("DIAG: FILTERED dup dest=" + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1431,6 +1469,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
allowed = true;
|
allowed = true;
|
||||||
}
|
}
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
|
DEBUG("BOUNDARY: BLOCKED backbone pkt dest=" + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()) + " hdr=" + std::to_string(packet.header_type()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// === TRANSITIVE WHITELIST ===
|
// === TRANSITIVE WHITELIST ===
|
||||||
@@ -1474,9 +1513,41 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CBA ACCUMULATES
|
// By default, remember packet hashes to avoid routing
|
||||||
_packet_hashlist.insert(packet.packet_hash());
|
// loops in the network, using the packet filter.
|
||||||
|
bool remember_packet_hash = true;
|
||||||
|
|
||||||
|
// If this packet belongs to a link in our link table,
|
||||||
|
// we'll have to defer adding it to the filter list.
|
||||||
|
// In some cases, we might see a packet over a shared-
|
||||||
|
// medium interface, belonging to a link that transports
|
||||||
|
// or terminates with this instance, but before it would
|
||||||
|
// normally reach us. If the packet is appended to the
|
||||||
|
// filter list at this point, link transport will break.
|
||||||
|
if (_link_table.find(packet.destination_hash()) != _link_table.end()) {
|
||||||
|
remember_packet_hash = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a link request proof, don't add it until
|
||||||
|
// we are sure it's not actually somewhere else in the
|
||||||
|
// routing chain.
|
||||||
|
if (packet.packet_type() == Type::Packet::PROOF && packet.context() == Type::Packet::LRPROOF) {
|
||||||
|
remember_packet_hash = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remember_packet_hash) {
|
||||||
|
// CBA ACCUMULATES
|
||||||
|
_packet_hashlist.insert(packet.packet_hash());
|
||||||
|
}
|
||||||
cache_packet(packet);
|
cache_packet(packet);
|
||||||
|
|
||||||
|
#ifdef BOUNDARY_MODE
|
||||||
|
// Log ALL non-announce packets arriving from local (non-backbone) interfaces
|
||||||
|
// to diagnose whether Sideband is sending ANY response packets
|
||||||
|
if (!is_backbone_interface(packet.receiving_interface()) && packet.packet_type() != Type::Packet::ANNOUNCE) {
|
||||||
|
DEBUG("LOCAL-IN: dest=" + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()) + " hdr=" + std::to_string(packet.header_type()) + " hops=" + std::to_string(packet.hops()) + " sz=" + std::to_string(packet.raw().size()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Check special conditions for local clients connected
|
// Check special conditions for local clients connected
|
||||||
// through a shared Reticulum instance
|
// through a shared Reticulum instance
|
||||||
@@ -1604,7 +1675,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
auto destination_iter = _destination_table.find(packet.destination_hash());
|
auto destination_iter = _destination_table.find(packet.destination_hash());
|
||||||
if (destination_iter != _destination_table.end()) {
|
if (destination_iter != _destination_table.end()) {
|
||||||
TRACE("Transport::inbound: Found next-hop path to destination");
|
TRACE("Transport::inbound: Found next-hop path to destination");
|
||||||
DestinationEntry destination_entry = (*destination_iter).second;
|
DestinationEntry& destination_entry = (*destination_iter).second;
|
||||||
Bytes next_hop = destination_entry._received_from;
|
Bytes next_hop = destination_entry._received_from;
|
||||||
uint8_t remaining_hops = destination_entry._hops;
|
uint8_t remaining_hops = destination_entry._hops;
|
||||||
|
|
||||||
@@ -1695,10 +1766,11 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
else {
|
else {
|
||||||
#ifdef BOUNDARY_MODE
|
#ifdef BOUNDARY_MODE
|
||||||
// BOUNDARY MODE: No path to destination. If packet came from
|
// BOUNDARY MODE: No path to destination. If packet came from
|
||||||
// a local device (non-backbone), request the path.
|
// a local device (non-backbone), request the path — but only if
|
||||||
|
// this isn't a link_id (link data is handled by link transport).
|
||||||
{
|
{
|
||||||
bool from_backbone = is_backbone_interface(packet.receiving_interface());
|
bool from_backbone = is_backbone_interface(packet.receiving_interface());
|
||||||
if (!from_backbone) {
|
if (!from_backbone && _link_table.find(packet.destination_hash()) == _link_table.end()) {
|
||||||
DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local device packet. Requesting path.");
|
DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local device packet. Requesting path.");
|
||||||
request_path(packet.destination_hash());
|
request_path(packet.destination_hash());
|
||||||
}
|
}
|
||||||
@@ -1796,8 +1868,13 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
dest_entry._timestamp = OS::time();
|
dest_entry._timestamp = OS::time();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local packet. Requesting path.");
|
// Only request path if the destination is not a link_id
|
||||||
request_path(packet.destination_hash());
|
// (link data packets are handled by link transport below,
|
||||||
|
// not by standard transport path lookup).
|
||||||
|
if (_link_table.find(packet.destination_hash()) == _link_table.end()) {
|
||||||
|
DEBUG("BOUNDARY: No path to " + packet.destination_hash().toHex() + " for local packet. Requesting path.");
|
||||||
|
request_path(packet.destination_hash());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1867,8 +1944,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
TRACE("Transport::inbound: Checking if packet is meant for link transport...");
|
TRACE("Transport::inbound: Checking if packet is meant for link transport...");
|
||||||
auto link_iter = _link_table.find(packet.destination_hash());
|
auto link_iter = _link_table.find(packet.destination_hash());
|
||||||
if (link_iter != _link_table.end()) {
|
if (link_iter != _link_table.end()) {
|
||||||
TRACE("Transport::inbound: Found link entry, handling link transport");
|
DEBUG("LINK-XPORT: pkt for " + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()) + " hops=" + std::to_string(packet.hops()) + " from=" + packet.receiving_interface().toString() + " hdr=" + std::to_string(packet.header_type()) + " sz=" + std::to_string(packet.raw().size()));
|
||||||
LinkEntry link_entry = (*link_iter).second;
|
LinkEntry& link_entry = (*link_iter).second;
|
||||||
|
DEBUG("LINK-XPORT: entry hops=" + std::to_string(link_entry._hops) + " rem=" + std::to_string(link_entry._remaining_hops) + " recv=" + link_entry._receiving_interface.toString() + " out=" + link_entry._outbound_interface.toString() + " val=" + std::to_string(link_entry._validated));
|
||||||
// If receiving and outbound interface is
|
// If receiving and outbound interface is
|
||||||
// the same for this link, direction doesn't
|
// the same for this link, direction doesn't
|
||||||
// matter, and we simply send the packet on.
|
// matter, and we simply send the packet on.
|
||||||
@@ -1888,21 +1966,32 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
if (packet.receiving_interface() == link_entry._outbound_interface) {
|
if (packet.receiving_interface() == link_entry._outbound_interface) {
|
||||||
// Also check that expected hop count matches
|
// Also check that expected hop count matches
|
||||||
if (packet.hops() == link_entry._remaining_hops) {
|
if (packet.hops() == link_entry._remaining_hops) {
|
||||||
TRACE("Transport::inbound: Link transporting on inbound interface");
|
|
||||||
outbound_interface = link_entry._receiving_interface;
|
outbound_interface = link_entry._receiving_interface;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
DEBUG("LINK-XPORT: HOP MISMATCH (from outbound) pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._remaining_hops));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (packet.receiving_interface() == link_entry._receiving_interface) {
|
else if (packet.receiving_interface() == link_entry._receiving_interface) {
|
||||||
// Also check that expected hop count matches
|
// Also check that expected hop count matches
|
||||||
if (packet.hops() == link_entry._hops) {
|
if (packet.hops() == link_entry._hops) {
|
||||||
TRACE("Transport::inbound: Link transporting on outbound interface");
|
|
||||||
outbound_interface = link_entry._outbound_interface;
|
outbound_interface = link_entry._outbound_interface;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
DEBUG("LINK-XPORT: HOP MISMATCH (from receiving) pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._hops));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DEBUG("LINK-XPORT: IFACE MISMATCH recv=" + packet.receiving_interface().toString() + " entry_recv=" + link_entry._receiving_interface.toString() + " entry_out=" + link_entry._outbound_interface.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outbound_interface) {
|
if (outbound_interface) {
|
||||||
TRACE("Transport::inbound: Transmitting link transport packet");
|
DEBUG("LINK-XPORT: FWD to " + outbound_interface.toString());
|
||||||
|
// Add this packet to the filter hashlist now that
|
||||||
|
// we have determined it's actually our turn to
|
||||||
|
// process it (matching Python Transport line 1544).
|
||||||
|
_packet_hashlist.insert(packet.packet_hash());
|
||||||
// CBA RESERVE
|
// CBA RESERVE
|
||||||
//Bytes new_raw;
|
//Bytes new_raw;
|
||||||
Bytes new_raw(512);
|
Bytes new_raw(512);
|
||||||
@@ -1916,7 +2005,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
link_entry._timestamp = OS::time();
|
link_entry._timestamp = OS::time();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//p pass
|
DEBUG("LINK-XPORT: DROPPED (no outbound interface resolved)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1931,6 +2020,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
// of queued announce rebroadcasts once handed to the next node.
|
// of queued announce rebroadcasts once handed to the next node.
|
||||||
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
|
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
|
||||||
TRACE("Transport::inbound: Packet is ANNOUNCE");
|
TRACE("Transport::inbound: Packet is ANNOUNCE");
|
||||||
|
DEBUG("DIAG: ANNOUNCE-IN dest=" + packet.destination_hash().toHex().substr(0,8) + " iface=" + packet.receiving_interface().toString() + " hops=" + std::to_string(packet.hops()));
|
||||||
Bytes received_from;
|
Bytes received_from;
|
||||||
//p local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
//p local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
||||||
#if defined(DESTINATIONS_SET)
|
#if defined(DESTINATIONS_SET)
|
||||||
@@ -2190,7 +2280,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
block_rebroadcasts,
|
block_rebroadcasts,
|
||||||
attached_interface
|
attached_interface
|
||||||
);
|
);
|
||||||
// CBA ACCUMULATES
|
// BUG FIX: erase before insert since std::map::insert() is
|
||||||
|
// a no-op when key exists (Python dict assignment overwrites)
|
||||||
|
_announce_table.erase(packet.destination_hash());
|
||||||
_announce_table.insert({packet.destination_hash(), announce_entry});
|
_announce_table.insert({packet.destination_hash(), announce_entry});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2219,7 +2311,9 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
block_rebroadcasts,
|
block_rebroadcasts,
|
||||||
attached_interface
|
attached_interface
|
||||||
);
|
);
|
||||||
// CBA ACCUMULATES
|
// BUG FIX: erase before insert since std::map::insert() is
|
||||||
|
// a no-op when key exists (Python dict assignment overwrites)
|
||||||
|
_announce_table.erase(packet.destination_hash());
|
||||||
_announce_table.insert({packet.destination_hash(), announce_entry});
|
_announce_table.insert({packet.destination_hash(), announce_entry});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2347,6 +2441,7 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG("Destination " + packet.destination_hash().toHex() + " is now " + std::to_string(announce_hops) + " hops away via " + received_from.toHex() + " on " + packet.receiving_interface().toString());
|
DEBUG("Destination " + packet.destination_hash().toHex() + " is now " + std::to_string(announce_hops) + " hops away via " + received_from.toHex() + " on " + packet.receiving_interface().toString());
|
||||||
|
DEBUG("DIAG: STORED path " + packet.destination_hash().toHex().substr(0,8) + " hops=" + std::to_string(announce_hops) + " iface=" + packet.receiving_interface().toString());
|
||||||
|
|
||||||
// BOUNDARY MODE: Register destinations seen via non-backbone interfaces (Whitelist 1)
|
// BOUNDARY MODE: Register destinations seen via non-backbone interfaces (Whitelist 1)
|
||||||
#ifdef BOUNDARY_MODE
|
#ifdef BOUNDARY_MODE
|
||||||
@@ -2521,16 +2616,16 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
// This is a link request proof, check if it
|
// This is a link request proof, check if it
|
||||||
// needs to be transported
|
// needs to be transported
|
||||||
if ((Reticulum::transport_enabled() || for_local_client_link || from_local_client) && _link_table.find(packet.destination_hash()) != _link_table.end()) {
|
if ((Reticulum::transport_enabled() || for_local_client_link || from_local_client) && _link_table.find(packet.destination_hash()) != _link_table.end()) {
|
||||||
TRACE("Handling link request proof...");
|
DEBUG("LRPROOF-XPORT: handling proof for link " + packet.destination_hash().toHex().substr(0,8));
|
||||||
LinkEntry link_entry = (*_link_table.find(packet.destination_hash())).second;
|
LinkEntry& link_entry = (*_link_table.find(packet.destination_hash())).second;
|
||||||
DEBUG("PROOF DEBUG: recv_iface=" + packet.receiving_interface().toString() + " out_iface=" + link_entry._outbound_interface.toString());
|
DEBUG("LRPROOF-XPORT: recv_iface=" + packet.receiving_interface().toString() + " entry_out=" + link_entry._outbound_interface.toString() + " entry_recv=" + link_entry._receiving_interface.toString());
|
||||||
|
|
||||||
bool interface_match = (packet.receiving_interface() == link_entry._outbound_interface);
|
bool interface_match = (packet.receiving_interface() == link_entry._outbound_interface);
|
||||||
if (interface_match) {
|
if (interface_match) {
|
||||||
try {
|
try {
|
||||||
size_t expected_size = (Type::Identity::SIGLENGTH/8 + Type::Link::ECPUBSIZE/2);
|
size_t expected_size = (Type::Identity::SIGLENGTH/8 + Type::Link::ECPUBSIZE/2);
|
||||||
size_t expected_size_with_mtu = expected_size + Type::Link::LINK_MTU_SIZE;
|
size_t expected_size_with_mtu = expected_size + Type::Link::LINK_MTU_SIZE;
|
||||||
DEBUG("PROOF DEBUG: data_size=" + std::to_string(packet.data().size()) + " expected=" + std::to_string(expected_size) + " or " + std::to_string(expected_size_with_mtu) + " raw_size=" + std::to_string(packet.raw().size()));
|
DEBUG("LRPROOF-XPORT: data_size=" + std::to_string(packet.data().size()) + " expected=" + std::to_string(expected_size) + " or " + std::to_string(expected_size_with_mtu));
|
||||||
if (packet.data().size() == expected_size || packet.data().size() == expected_size_with_mtu) {
|
if (packet.data().size() == expected_size || packet.data().size() == expected_size_with_mtu) {
|
||||||
Bytes signalling_bytes;
|
Bytes signalling_bytes;
|
||||||
if (packet.data().size() == expected_size_with_mtu) {
|
if (packet.data().size() == expected_size_with_mtu) {
|
||||||
@@ -2540,17 +2635,17 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
Bytes peer_pub_bytes = packet.data().mid(Type::Identity::SIGLENGTH/8, Type::Link::ECPUBSIZE/2);
|
Bytes peer_pub_bytes = packet.data().mid(Type::Identity::SIGLENGTH/8, Type::Link::ECPUBSIZE/2);
|
||||||
Identity peer_identity = Identity::recall(link_entry._destination_hash);
|
Identity peer_identity = Identity::recall(link_entry._destination_hash);
|
||||||
if (!peer_identity) {
|
if (!peer_identity) {
|
||||||
DEBUG("PROOF DEBUG: Cannot recall identity for " + link_entry._destination_hash.toHex() + ", dropping proof.");
|
DEBUG("LRPROOF-XPORT: FAILED cannot recall identity for " + link_entry._destination_hash.toHex().substr(0,8));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG("PROOF DEBUG: peer_identity recalled for " + link_entry._destination_hash.toHex() + " valid=" + std::to_string(!peer_identity.get_public_key().empty()));
|
DEBUG("LRPROOF-XPORT: peer identity recalled for " + link_entry._destination_hash.toHex().substr(0,8));
|
||||||
Bytes peer_sig_pub_bytes = peer_identity.get_public_key().mid(Type::Link::ECPUBSIZE/2, Type::Link::ECPUBSIZE/2);
|
Bytes peer_sig_pub_bytes = peer_identity.get_public_key().mid(Type::Link::ECPUBSIZE/2, Type::Link::ECPUBSIZE/2);
|
||||||
|
|
||||||
Bytes signed_data = packet.destination_hash() + peer_pub_bytes + peer_sig_pub_bytes + signalling_bytes;
|
Bytes signed_data = packet.destination_hash() + peer_pub_bytes + peer_sig_pub_bytes + signalling_bytes;
|
||||||
Bytes signature = packet.data().left(Type::Identity::SIGLENGTH/8);
|
Bytes signature = packet.data().left(Type::Identity::SIGLENGTH/8);
|
||||||
|
|
||||||
if (peer_identity.validate(signature, signed_data)) {
|
if (peer_identity.validate(signature, signed_data)) {
|
||||||
TRACE("Link request proof validated for transport via " + link_entry._receiving_interface.toString());
|
DEBUG("LRPROOF-XPORT: VALIDATED, forwarding to " + link_entry._receiving_interface.toString());
|
||||||
//p new_raw = packet.raw[0:1]
|
//p new_raw = packet.raw[0:1]
|
||||||
// CBA RESERVE
|
// CBA RESERVE
|
||||||
//Bytes new_raw = packet.raw().left(1);
|
//Bytes new_raw = packet.raw().left(1);
|
||||||
@@ -2560,28 +2655,33 @@ static bool is_backbone_interface(const Interface& iface) {
|
|||||||
new_raw << packet.hops();
|
new_raw << packet.hops();
|
||||||
//p new_raw += packet.raw[2:]
|
//p new_raw += packet.raw[2:]
|
||||||
new_raw << packet.raw().mid(2);
|
new_raw << packet.raw().mid(2);
|
||||||
|
DEBUG("LRPROOF-XPORT: new_raw size=" + std::to_string(new_raw.size()) + " hops=" + std::to_string(packet.hops()) + " flags=0x" + new_raw.left(1).toHex() + " dest=" + new_raw.mid(2, 10).toHex().substr(0,16));
|
||||||
link_entry._validated = true;
|
link_entry._validated = true;
|
||||||
transmit(link_entry._receiving_interface, new_raw);
|
transmit(link_entry._receiving_interface, new_raw);
|
||||||
|
DEBUG("LRPROOF-XPORT: transmit() returned OK");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG("Invalid link request proof in transport for link " + packet.destination_hash().toHex() + ", dropping proof.");
|
DEBUG("LRPROOF-XPORT: INVALID signature for link " + packet.destination_hash().toHex().substr(0,8) + ", dropping proof.");
|
||||||
}
|
}
|
||||||
} // end peer_identity valid
|
} // end peer_identity valid
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
DEBUG("LRPROOF-XPORT: UNEXPECTED data_size=" + std::to_string(packet.data().size()) + " (expected " + std::to_string(expected_size) + " or " + std::to_string(expected_size_with_mtu) + "), dropping proof.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception& e) {
|
||||||
ERROR("Error while transporting link request proof. The contained exception was: " + std::string(e.what()));
|
ERROR("Error while transporting link request proof. The contained exception was: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG("PROOF DEBUG: interface mismatch - recv=" + packet.receiving_interface().toString() + " expected_out=" + link_entry._outbound_interface.toString());
|
DEBUG("LRPROOF-XPORT: IFACE MISMATCH recv=" + packet.receiving_interface().toString() + " expected_out=" + link_entry._outbound_interface.toString());
|
||||||
DEBUG("Link request proof received on wrong interface, not transporting it.");
|
DEBUG("LRPROOF-XPORT: Proof received on wrong interface, not transporting.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Check if we can deliver it to a local
|
// Not in link_table or transport not enabled — check
|
||||||
// pending link
|
// if we can deliver it to a local pending link
|
||||||
TRACEF("Handling proof for link request %s", packet.destination_hash().toHex().c_str());
|
DEBUG("LRPROOF-XPORT: not in link_table or transport not enabled, checking local pending links (transport=" + std::to_string(Reticulum::transport_enabled()) + " for_lcl=" + std::to_string(for_local_client_link) + " from_lcl=" + std::to_string(from_local_client) + " in_lt=" + std::to_string(_link_table.find(packet.destination_hash()) != _link_table.end()) + ")");
|
||||||
// CBA Must make a copy of _pending_links before traversing since it gets modified
|
// CBA Must make a copy of _pending_links before traversing since it gets modified
|
||||||
//for (auto link : _pending_links) {
|
//for (auto link : _pending_links) {
|
||||||
std::set<Link> pending_links(_pending_links);
|
std::set<Link> pending_links(_pending_links);
|
||||||
@@ -3369,6 +3469,7 @@ will announce it.
|
|||||||
|
|
||||||
/*static*/ void Transport::path_request_handler(const Bytes& data, const Packet& packet) {
|
/*static*/ void Transport::path_request_handler(const Bytes& data, const Packet& packet) {
|
||||||
TRACE("Transport::path_request_handler");
|
TRACE("Transport::path_request_handler");
|
||||||
|
if (data.size() >= 16) { DEBUG("DIAG: PATH-REQ for " + data.left(16).toHex().substr(0,8) + " from " + packet.receiving_interface().toString()); }
|
||||||
try {
|
try {
|
||||||
// If there is at least bytes enough for a destination
|
// If there is at least bytes enough for a destination
|
||||||
// hash in the packet, we assume those bytes are the
|
// hash in the packet, we assume those bytes are the
|
||||||
@@ -3505,13 +3606,21 @@ will announce it.
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", path is known");
|
DEBUG("Answering path request for destination " + destination_hash.toHex() + interface_str + ", path is known");
|
||||||
|
DEBUG("DIAG: PATH-RESP for " + destination_hash.toHex().substr(0,8) + interface_str);
|
||||||
|
|
||||||
double now = OS::time();
|
double now = OS::time();
|
||||||
uint8_t retries = Type::Transport::PATHFINDER_R;
|
uint8_t retries = Type::Transport::PATHFINDER_R;
|
||||||
uint8_t local_rebroadcasts = 0;
|
uint8_t local_rebroadcasts = 0;
|
||||||
bool block_rebroadcasts = true;
|
bool block_rebroadcasts = true;
|
||||||
// CBA TODO Determine if okay to take hops directly from DestinationEntry
|
// BUG FIX: Must use DestinationEntry._hops, NOT cached announce_packet.hops().
|
||||||
uint8_t announce_hops = announce_packet.hops();
|
// The cached packet's raw bytes still have the pre-increment wire hops,
|
||||||
|
// because Packet::hops(val) only updates an in-memory field, not raw[1].
|
||||||
|
// Python Transport.py explicitly sets packet.hops = path_table[dest][IDX_PT_HOPS]
|
||||||
|
// after retrieving the cached packet (line 2736), but C++ was using the
|
||||||
|
// stale raw-byte value. This caused PATH_RESPONSE to report fewer hops than
|
||||||
|
// actual, making the sender's expected_hops too low, which then caused
|
||||||
|
// LRPROOF hop-count validation to fail silently.
|
||||||
|
uint8_t announce_hops = destination_entry._hops;
|
||||||
|
|
||||||
double retransmit_timeout = 0;
|
double retransmit_timeout = 0;
|
||||||
if (is_from_local_client) {
|
if (is_from_local_client) {
|
||||||
@@ -3533,30 +3642,16 @@ will announce it.
|
|||||||
AnnounceEntry& held_entry = (*announce_iter).second;
|
AnnounceEntry& held_entry = (*announce_iter).second;
|
||||||
// CBA ACCUMULATES
|
// CBA ACCUMULATES
|
||||||
_held_announces.insert({announce_packet.destination_hash(), held_entry});
|
_held_announces.insert({announce_packet.destination_hash(), held_entry});
|
||||||
|
// BUG FIX: Must erase old entry before insert(),
|
||||||
|
// since std::map::insert() is a no-op when key exists.
|
||||||
|
// Python dict assignment overwrites, but C++ insert does not.
|
||||||
|
_announce_table.erase(announce_iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// CBA ACCUMULATES
|
|
||||||
_announce_table.insert({announce_packet.destination_hash(), {
|
|
||||||
now,
|
|
||||||
retransmit_timeout,
|
|
||||||
retries,
|
|
||||||
// BUG?
|
|
||||||
//destination_entry.receiving_interface,
|
|
||||||
destination_entry._received_from,
|
|
||||||
announce_hops,
|
|
||||||
announce_packet,
|
|
||||||
local_rebroadcasts,
|
|
||||||
block_rebroadcasts,
|
|
||||||
attached_interface
|
|
||||||
}});
|
|
||||||
*/
|
|
||||||
AnnounceEntry announce_entry(
|
AnnounceEntry announce_entry(
|
||||||
now,
|
now,
|
||||||
retransmit_timeout,
|
retransmit_timeout,
|
||||||
retries,
|
retries,
|
||||||
// BUG?
|
|
||||||
//destination_entry.receiving_interface,
|
|
||||||
destination_entry._received_from,
|
destination_entry._received_from,
|
||||||
announce_hops,
|
announce_hops,
|
||||||
announce_packet,
|
announce_packet,
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ board_build.partitions = default_8MB.csv
|
|||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
board_build.psram_type = qio
|
board_build.psram_type = qio
|
||||||
board_build.arduino.memory_type = qio_qspi
|
board_build.arduino.memory_type = qio_qspi
|
||||||
monitor_speed = 115200
|
monitor_speed = 921600
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
-DBOARD_MODEL=BOARD_HELTEC32_V3
|
-DBOARD_MODEL=BOARD_HELTEC32_V3
|
||||||
|
|||||||
Reference in New Issue
Block a user