MTU Clamping (Bug 8a): Clamp link MTU signalling in LINKREQUEST packets when forwarding through transport node, matching Python reference impl. Without this, TCP endpoints negotiate 8192-byte segments that exceed the V3's 1064-byte HDLC buffer, causing silent truncation and permanent resource transfer stalls at ~70%. Fixed MTU declaration (Bug 8b): Set FIXED_MTU=true on TcpInterface so Transport uses HW_MTU for clamping decisions. Oversized frame detection (Bug 8c): Track truncated HDLC frames and drop them with a diagnostic log instead of silently delivering corrupt data to Transport. Echo-back prevention (v1.0.10): Track which TCP client originated each inbound frame and skip that client in send_outgoing() to prevent flooding TCP buffers. Register local client interface: Enable Transport forwarding of announces, link packets, and proofs to TCP clients. Document all discovered microReticulum bugs in MICRORETICULUM_BUGS.md.
506 lines
20 KiB
C++
Executable File
506 lines
20 KiB
C++
Executable File
#pragma once
|
|
|
|
#include "Packet.h"
|
|
#include "Bytes.h"
|
|
#include "Type.h"
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
#include <list>
|
|
#include <set>
|
|
#include <array>
|
|
#include <memory>
|
|
#include <functional>
|
|
#include <stdint.h>
|
|
|
|
//#define INTERFACES_SET
|
|
//#define INTERFACES_LIST
|
|
#define INTERFACES_MAP
|
|
|
|
//#define DESTINATIONS_SET
|
|
#define DESTINATIONS_MAP
|
|
|
|
namespace RNS {
|
|
|
|
class Reticulum;
|
|
class Identity;
|
|
class Destination;
|
|
class Interface;
|
|
class Link;
|
|
class Packet;
|
|
class PacketReceipt;
|
|
|
|
class AnnounceHandler {
|
|
public:
|
|
// The initialisation method takes the optional
|
|
// aspect_filter argument. If aspect_filter is set to
|
|
// None, all announces will be passed to the instance.
|
|
// If only some announces are wanted, it can be set to
|
|
// an aspect string.
|
|
AnnounceHandler(const char* aspect_filter = nullptr) { if (aspect_filter != nullptr) _aspect_filter = aspect_filter; }
|
|
// This method will be called by Reticulums Transport
|
|
// system when an announce arrives that matches the
|
|
// configured aspect filter. Filters must be specific,
|
|
// and cannot use wildcards.
|
|
virtual void received_announce(const Bytes& destination_hash, const Identity& announced_identity, const Bytes& app_data) = 0;
|
|
std::string& aspect_filter() { return _aspect_filter; }
|
|
private:
|
|
std::string _aspect_filter;
|
|
};
|
|
using HAnnounceHandler = std::shared_ptr<AnnounceHandler>;
|
|
|
|
/*
|
|
Through static methods of this class you can interact with the
|
|
Transport system of Reticulum.
|
|
*/
|
|
class Transport {
|
|
|
|
public:
|
|
class Callbacks {
|
|
public:
|
|
using receive_packet = void(*)(const Bytes& raw, const Interface& interface);
|
|
using transmit_packet = void(*)(const Bytes& raw, const Interface& interface);
|
|
using filter_packet = bool(*)(const Packet& packet);
|
|
public:
|
|
receive_packet _receive_packet = nullptr;
|
|
transmit_packet _transmit_packet = nullptr;
|
|
filter_packet _filter_packet = nullptr;
|
|
friend class Transport;
|
|
};
|
|
|
|
public:
|
|
|
|
class PacketEntry {
|
|
public:
|
|
PacketEntry() {}
|
|
PacketEntry(const Bytes& raw, double sent_at, const Bytes& destination_hash) :
|
|
_raw(raw),
|
|
_sent_at(sent_at),
|
|
_destination_hash(destination_hash)
|
|
{
|
|
}
|
|
PacketEntry(const Packet& packet) :
|
|
_raw(packet.raw()),
|
|
_sent_at(packet.sent_at()),
|
|
_destination_hash(packet.destination_hash())
|
|
{
|
|
}
|
|
public:
|
|
Bytes _raw;
|
|
double _sent_at = 0;
|
|
Bytes _destination_hash;
|
|
bool _cached = false;
|
|
#ifndef NDEBUG
|
|
inline std::string debugString() const {
|
|
std::string dump;
|
|
dump = "PacketEntry: destination_hash=" + _destination_hash.toHex() +
|
|
" sent_at=" + std::to_string(_sent_at);
|
|
return dump;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
// CBA TODO Analyze safety of using Packet references here
|
|
class DestinationEntry {
|
|
public:
|
|
DestinationEntry() {}
|
|
DestinationEntry(double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, const Bytes& receiving_interface, const Bytes& packet) :
|
|
_timestamp(timestamp),
|
|
_received_from(received_from),
|
|
_hops(announce_hops),
|
|
_expires(expires),
|
|
_random_blobs(random_blobs),
|
|
_receiving_interface(receiving_interface),
|
|
_announce_packet(packet)
|
|
{
|
|
}
|
|
public:
|
|
inline Interface receiving_interface() const { return find_interface_from_hash(_receiving_interface); }
|
|
inline Packet announce_packet() const { return get_cached_packet(_announce_packet); }
|
|
public:
|
|
double _timestamp = 0;
|
|
Bytes _received_from;
|
|
uint8_t _hops = 0;
|
|
double _expires = 0;
|
|
std::set<Bytes> _random_blobs;
|
|
//Interface _receiving_interface = {Type::NONE};
|
|
Bytes _receiving_interface;
|
|
//const Packet& _announce_packet;
|
|
//Packet _announce_packet = {Type::NONE};
|
|
Bytes _announce_packet;
|
|
inline bool operator < (const DestinationEntry& entry) const {
|
|
// sort by ascending timestamp (oldest entries at the top)
|
|
return _timestamp < entry._timestamp;
|
|
}
|
|
#ifndef NDEBUG
|
|
inline std::string debugString() const {
|
|
std::string dump;
|
|
dump = "DestinationEntry: timestamp=" + std::to_string(_timestamp) +
|
|
" received_from=" + _received_from.toHex() +
|
|
" hops=" + std::to_string(_hops) +
|
|
" expires=" + std::to_string(_expires) +
|
|
//" random_blobs=" + _random_blobs +
|
|
" receiving_interface=" + _receiving_interface.toHex() +
|
|
" announce_packet=" + _announce_packet.toHex();
|
|
dump += " random_blobs=(";
|
|
for (auto& blob : _random_blobs) {
|
|
dump += blob.toHex() + ",";
|
|
}
|
|
dump += ")";
|
|
return dump;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
// CBA TODO Analyze safety of using Packet references here
|
|
class AnnounceEntry {
|
|
public:
|
|
AnnounceEntry(double timestamp, double retransmit_timeout, uint8_t retries, const Bytes& received_from, uint8_t hops, const Packet& packet, uint8_t local_rebroadcasts, bool block_rebroadcasts, const Interface& attached_interface) :
|
|
_timestamp(timestamp),
|
|
_retransmit_timeout(retransmit_timeout),
|
|
_retries(retries),
|
|
_received_from(received_from),
|
|
_hops(hops),
|
|
_packet(packet),
|
|
_local_rebroadcasts(local_rebroadcasts),
|
|
_block_rebroadcasts(block_rebroadcasts),
|
|
_attached_interface(attached_interface)
|
|
{
|
|
}
|
|
public:
|
|
double _timestamp = 0;
|
|
double _retransmit_timeout = 0;
|
|
uint8_t _retries = 0;
|
|
const Bytes _received_from;
|
|
uint8_t _hops = 0;
|
|
// CBA Storing packet reference causes memory issues, presumably because orignal packet is being destroyed
|
|
// MUST use instance instad of reference!!!
|
|
//const Packet& _packet;
|
|
const Packet _packet = {Type::NONE};
|
|
uint8_t _local_rebroadcasts = 0;
|
|
bool _block_rebroadcasts = false;
|
|
const Interface _attached_interface = {Type::NONE};
|
|
};
|
|
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
class LinkEntry {
|
|
public:
|
|
LinkEntry(double timestamp, const Bytes& next_hop, const Interface& outbound_interface, uint8_t remaining_hops, const Interface& receiving_interface, uint8_t hops, const Bytes& destination_hash, bool validated, double proof_timeout) :
|
|
_timestamp(timestamp),
|
|
_next_hop(next_hop),
|
|
_outbound_interface(outbound_interface),
|
|
_remaining_hops(remaining_hops),
|
|
_receiving_interface(receiving_interface),
|
|
_hops(hops),
|
|
_destination_hash(destination_hash),
|
|
_validated(validated),
|
|
_proof_timeout(proof_timeout)
|
|
{
|
|
}
|
|
public:
|
|
double _timestamp = 0;
|
|
const Bytes _next_hop;
|
|
const Interface _outbound_interface = {Type::NONE};
|
|
uint8_t _remaining_hops = 0;
|
|
Interface _receiving_interface = {Type::NONE};
|
|
uint8_t _hops = 0;
|
|
const Bytes _destination_hash;
|
|
bool _validated = false;
|
|
double _proof_timeout = 0;
|
|
};
|
|
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
class ReverseEntry {
|
|
public:
|
|
ReverseEntry(const Interface& receiving_interface, const Interface& outbound_interface, double timestamp) :
|
|
_receiving_interface(receiving_interface),
|
|
_outbound_interface(outbound_interface),
|
|
_timestamp(timestamp)
|
|
{
|
|
}
|
|
public:
|
|
Interface _receiving_interface = {Type::NONE};
|
|
const Interface _outbound_interface = {Type::NONE};
|
|
double _timestamp = 0;
|
|
};
|
|
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
class PathRequestEntry {
|
|
public:
|
|
PathRequestEntry(const Bytes& destination_hash, double timeout, const Interface& requesting_interface) :
|
|
_destination_hash(destination_hash),
|
|
_timeout(timeout),
|
|
_requesting_interface(requesting_interface)
|
|
{
|
|
}
|
|
public:
|
|
const Bytes _destination_hash;
|
|
double _timeout = 0;
|
|
const Interface _requesting_interface = {Type::NONE};
|
|
};
|
|
|
|
/*
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
class SerialisedEntry {
|
|
public:
|
|
SerialisedEntry(const Bytes& destination_hash, double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, Interface& receiving_interface, const Packet& packet) :
|
|
_destination_hash(destination_hash),
|
|
_timestamp(timestamp),
|
|
_hops(announce_hops),
|
|
_expires(expires),
|
|
_random_blobs(random_blobs),
|
|
_receiving_interface(receiving_interface),
|
|
_announce_packet(packet)
|
|
{
|
|
}
|
|
public:
|
|
const Bytes _destination_hash;
|
|
double _timestamp = 0;
|
|
const Bytes _received_from;
|
|
uint8_t _hops = 0;
|
|
double _expires = 0;
|
|
std::set<Bytes> _random_blobs;
|
|
Interface _receiving_interface = {Type::NONE};
|
|
Packet _announce_packet = {Type::NONE};
|
|
};
|
|
*/
|
|
|
|
// CBA TODO Analyze safety of using Inrerface references here
|
|
class TunnelEntry {
|
|
public:
|
|
TunnelEntry(const Bytes& tunnel_id, const Bytes& interface_hash, double expires) :
|
|
_tunnel_id(tunnel_id),
|
|
_interface_hash(interface_hash),
|
|
_expires(expires)
|
|
{
|
|
}
|
|
public:
|
|
const Bytes _tunnel_id;
|
|
const Bytes _interface_hash;
|
|
std::map<Bytes, DestinationEntry> _serialised_paths;
|
|
double _expires = 0;
|
|
};
|
|
|
|
class RateEntry {
|
|
public:
|
|
RateEntry(double now) :
|
|
_last(now)
|
|
{
|
|
_timestamps.push_back(now);
|
|
}
|
|
public:
|
|
double _last = 0.0;
|
|
double _rate_violations = 0.0;
|
|
double _blocked_until = 0.0;
|
|
std::vector<double> _timestamps;
|
|
};
|
|
|
|
public:
|
|
static void start(const Reticulum& reticulum_instance);
|
|
static void loop();
|
|
static void jobs();
|
|
static void transmit(Interface& interface, const Bytes& raw);
|
|
static bool outbound(Packet& packet);
|
|
static bool packet_filter(const Packet& packet);
|
|
//static void inbound(const Bytes& raw, const Interface& interface = {Type::NONE});
|
|
static void inbound(const Bytes& raw, const Interface& interface);
|
|
static void inbound(const Bytes& raw);
|
|
static void synthesize_tunnel(const Interface& interface);
|
|
static void tunnel_synthesize_handler(const Bytes& data, const Packet& packet);
|
|
static void handle_tunnel(const Bytes& tunnel_id, const Interface& interface);
|
|
static void register_interface(Interface& interface);
|
|
static void deregister_interface(const Interface& interface);
|
|
static void register_local_client_interface(const Interface& interface) { _local_client_interfaces.insert(std::cref(interface)); }
|
|
inline static const std::map<Bytes, Interface&> get_interfaces() { return _interfaces; }
|
|
static void register_destination(Destination& destination);
|
|
static void deregister_destination(const Destination& destination);
|
|
static void register_link(Link& link);
|
|
static void activate_link(Link& link);
|
|
static void register_announce_handler(HAnnounceHandler handler);
|
|
static void deregister_announce_handler(HAnnounceHandler handler);
|
|
static Interface find_interface_from_hash(const Bytes& interface_hash);
|
|
static bool should_cache_packet(const Packet& packet);
|
|
static bool cache_packet(const Packet& packet, bool force_cache = false);
|
|
static Packet get_cached_packet(const Bytes& packet_hash);
|
|
static bool clear_cached_packet(const Bytes& packet_hash);
|
|
static bool cache_request_packet(const Packet& packet);
|
|
static void cache_request(const Bytes& packet_hash, const Destination& destination);
|
|
static bool remove_path(const Bytes& destination_hash);
|
|
static bool has_path(const Bytes& destination_hash);
|
|
static uint8_t hops_to(const Bytes& destination_hash);
|
|
static Bytes next_hop(const Bytes& destination_hash);
|
|
static Interface next_hop_interface(const Bytes& destination_hash);
|
|
static uint32_t next_hop_interface_bitrate(const Bytes& destination_hash);
|
|
static uint16_t next_hop_interface_hw_mtu(const Bytes& destination_hash);
|
|
static double next_hop_per_bit_latency(const Bytes& destination_hash);
|
|
static double next_hop_per_byte_latency(const Bytes& destination_hash);
|
|
static double first_hop_timeout(const Bytes& destination_hash);
|
|
static double extra_link_proof_timeout(const Interface& interface);
|
|
static bool expire_path(const Bytes& destination_hash);
|
|
//static void request_path(const Bytes& destination_hash, const Interface& on_interface = {Type::NONE}, const Bytes& tag = {}, bool recursive = false);
|
|
static void request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag = {}, bool recursive = false);
|
|
static void request_path(const Bytes& destination_hash);
|
|
static void path_request_handler(const Bytes& data, const Packet& packet);
|
|
static void path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id = {}, const Bytes& tag = {});
|
|
static bool from_local_client(const Packet& packet);
|
|
static bool is_local_client_interface(const Interface& interface);
|
|
static bool interface_to_shared_instance(const Interface& interface);
|
|
static void detach_interfaces();
|
|
static void shared_connection_disappeared();
|
|
static void shared_connection_reappeared();
|
|
static void drop_announce_queues();
|
|
static uint64_t announce_emitted(const Packet& packet);
|
|
static void write_packet_hashlist();
|
|
static bool read_path_table();
|
|
static bool write_path_table();
|
|
static void read_tunnel_table();
|
|
static void write_tunnel_table();
|
|
static void persist_data();
|
|
static void clean_caches();
|
|
static void dump_stats();
|
|
static void exit_handler();
|
|
|
|
static uint16_t remove_reverse_entries(const std::vector<Bytes>& hashes);
|
|
static uint16_t remove_links(const std::vector<Bytes>& hashes);
|
|
static uint16_t remove_paths(const std::vector<Bytes>& hashes);
|
|
static uint16_t remove_discovery_path_requests(const std::vector<Bytes>& hashes);
|
|
static uint16_t remove_tunnels(const std::vector<Bytes>& hashes);
|
|
|
|
static Destination find_destination_from_hash(const Bytes& destination_hash);
|
|
|
|
// CBA
|
|
static void cull_path_table();
|
|
|
|
// getters/setters
|
|
static inline void set_receive_packet_callback(Callbacks::receive_packet callback) { _callbacks._receive_packet = callback; }
|
|
static inline void set_transmit_packet_callback(Callbacks::transmit_packet callback) { _callbacks._transmit_packet = callback; }
|
|
static inline void set_filter_packet_callback(Callbacks::filter_packet callback) { _callbacks._filter_packet = callback; }
|
|
static inline const Reticulum& reticulum() { return _owner; }
|
|
static inline const Identity& identity() { return _identity; }
|
|
inline static uint16_t path_table_maxsize() { return _path_table_maxsize; }
|
|
inline static void path_table_maxsize(uint16_t path_table_maxsize) { _path_table_maxsize = path_table_maxsize; }
|
|
inline static uint16_t probe_destination_enabled() { return _path_table_maxpersist; }
|
|
inline static void path_table_maxpersist(uint16_t path_table_maxpersist) { _path_table_maxpersist = path_table_maxpersist; }
|
|
// CBA TEST
|
|
static inline void identity(Identity& identity) { _identity = identity; }
|
|
|
|
inline static const std::map<Bytes, DestinationEntry>& get_destination_table() { return _destination_table; }
|
|
inline static const std::map<Bytes, RateEntry>& get_announce_rate_table() { return _announce_rate_table; }
|
|
inline static const std::map<Bytes, LinkEntry>& get_link_table() { return _link_table; }
|
|
|
|
private:
|
|
// CBA MUST use references to interfaces here in order for virtul overrides for send/receive to work
|
|
#if defined(INTERFACES_SET)
|
|
// set sorted, can use find
|
|
//static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _interfaces; // All active interfaces
|
|
static std::set<std::reference_wrapper<Interface>, std::less<Interface>> _interfaces; // All active interfaces
|
|
#elif defined(INTERFACES_LIST)
|
|
// list is unsorted, can't use find
|
|
static std::list<std::reference_wrapper<Interface>> _interfaces; // All active interfaces
|
|
#elif defined(INTERFACES_MAP)
|
|
// map is sorted, can use find
|
|
static std::map<Bytes, Interface&> _interfaces; // All active interfaces
|
|
#endif
|
|
#if defined(DESTINATIONS_SET)
|
|
static std::set<Destination> _destinations; // All active destinations
|
|
#elif defined(DESTINATIONS_MAP)
|
|
static std::map<Bytes, Destination> _destinations; // All active destinations
|
|
#endif
|
|
// CBA TODO: Reconsider using std::set for enforcing uniqueness. Maybe consider std::map keyed on hash instead
|
|
static std::set<Link> _pending_links; // Links that are being established
|
|
static std::set<Link> _active_links; // Links that are active
|
|
static std::set<Bytes> _packet_hashlist; // A list of packet hashes for duplicate detection
|
|
static std::list<PacketReceipt> _receipts; // Receipts of all outgoing packets for proof processing
|
|
|
|
// TODO: "destination_table" should really be renamed to "path_table"
|
|
// Notes on memory usage: 1 megabyte of memory can store approximately
|
|
// 55.100 path table entries or approximately 22.300 link table entries.
|
|
|
|
static std::map<Bytes, AnnounceEntry> _announce_table; // A table for storing announces currently waiting to be retransmitted
|
|
static std::map<Bytes, DestinationEntry> _destination_table; // A lookup table containing the next hop to a given destination
|
|
static std::map<Bytes, ReverseEntry> _reverse_table; // A lookup table for storing packet hashes used to return proofs and replies
|
|
static std::map<Bytes, LinkEntry> _link_table; // A lookup table containing hops for links
|
|
static std::map<Bytes, AnnounceEntry> _held_announces; // A table containing temporarily held announce-table entries
|
|
static std::set<HAnnounceHandler> _announce_handlers; // A table storing externally registered announce handlers
|
|
static std::map<Bytes, TunnelEntry> _tunnels; // A table storing tunnels to other transport instances
|
|
static std::map<Bytes, RateEntry> _announce_rate_table; // A table for keeping track of announce rates
|
|
static std::map<Bytes, double> _path_requests; // A table for storing path request timestamps
|
|
|
|
static std::map<Bytes, PathRequestEntry> _discovery_path_requests; // A table for keeping track of path requests on behalf of other nodes
|
|
static std::set<Bytes> _discovery_pr_tags; // A table for keeping track of tagged path requests
|
|
|
|
// Transport control destinations are used
|
|
// for control purposes like path requests
|
|
static std::set<Destination> _control_destinations;
|
|
static std::set<Bytes> _control_hashes;
|
|
|
|
// Interfaces for communicating with
|
|
// local clients connected to a shared
|
|
// Reticulum instance
|
|
//static std::set<Interface> _local_client_interfaces;
|
|
static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _local_client_interfaces;
|
|
|
|
static std::map<Bytes, const Interface&> _pending_local_path_requests;
|
|
|
|
// CBA
|
|
static std::map<Bytes, PacketEntry> _packet_table; // A lookup table containing announce packets for known paths
|
|
|
|
//z _local_client_rssi_cache = []
|
|
//z _local_client_snr_cache = []
|
|
static uint16_t _LOCAL_CLIENT_CACHE_MAXSIZE;
|
|
|
|
static double _start_time;
|
|
static bool _jobs_locked;
|
|
static bool _jobs_running;
|
|
static float _job_interval;
|
|
static double _jobs_last_run;
|
|
static double _links_last_checked;
|
|
static float _links_check_interval;
|
|
static double _receipts_last_checked;
|
|
static float _receipts_check_interval;
|
|
static double _announces_last_checked;
|
|
static float _announces_check_interval;
|
|
static double _tables_last_culled;
|
|
static float _tables_cull_interval;
|
|
static bool _saving_path_table;
|
|
static uint16_t _hashlist_maxsize;
|
|
static uint16_t _max_pr_tags;
|
|
|
|
// CBA
|
|
static uint16_t _path_table_maxsize;
|
|
static uint16_t _path_table_maxpersist;
|
|
static double _last_saved;
|
|
static float _save_interval;
|
|
static uint32_t _destination_table_crc;
|
|
|
|
static Reticulum _owner;
|
|
static Identity _identity;
|
|
|
|
// CBA
|
|
static Callbacks _callbacks;
|
|
|
|
// CBA Stats
|
|
static uint32_t _packets_sent;
|
|
static uint32_t _packets_received;
|
|
static uint32_t _destinations_added;
|
|
static size_t _last_memory;
|
|
static size_t _last_flash;
|
|
};
|
|
|
|
template <typename M, typename S>
|
|
void MapToValues(const M& m, S& s) {
|
|
for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
|
|
s.insert(it->second);
|
|
}
|
|
}
|
|
|
|
template <typename M, typename S>
|
|
void MapToPairs(const M& m, S& s) {
|
|
for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
|
|
s.push_back(*it);
|
|
}
|
|
}
|
|
}
|