Files

231 lines
8.4 KiB
C++

#pragma once
#include <atomic>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include "session/network/backends/session_file_server.hpp"
#include "session/network/request_queue.hpp"
#include "session/network/routing/network_router.hpp"
#include "session/network/snode_pool.hpp"
namespace session::network {
namespace config {
struct OnionRequestRouter {
FileServer file_server_config;
std::optional<std::filesystem::path> cache_directory;
std::chrono::days edge_node_cache_duration;
opt::netid::Target netid;
std::vector<service_node> seed_nodes;
network::opt::retry_delay retry_delay;
uint8_t path_length;
uint8_t path_strike_threshold;
uint8_t path_build_retry_limit;
std::chrono::minutes path_rotation_frequency;
uint8_t node_strike_threshold;
bool disable_pre_build_paths;
bool single_path_mode;
std::unordered_map<PathCategory, uint8_t> min_path_counts;
};
} // namespace config
struct cached_edge_node {
service_node node;
std::chrono::system_clock::time_point first_connected_at;
template <typename OutputIt>
void to_disk(OutputIt out) const {
auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
first_connected_at.time_since_epoch())
.count();
fmt::format_to(
out,
"{}|{}|{}|{}|{}.{}.{}|{}|{}\n",
node.remote_pubkey.hex(),
node.host(),
node.https_port,
node.omq_port,
node.storage_server_version[0],
node.storage_server_version[1],
node.storage_server_version[2],
node.swarm_id,
timestamp);
}
static cached_edge_node from_disk(std::string_view str);
};
namespace cached_edge_node_disk_format {
constexpr size_t TIMESTAMP = 10; // unix timestamp
constexpr size_t MAX_LINE_SIZE = service_node_disk_format::MAX_LINE_SIZE + TIMESTAMP;
} // namespace cached_edge_node_disk_format
struct OnionPath {
std::string id;
std::vector<service_node> nodes;
std::chrono::system_clock::time_point created_at;
std::chrono::system_clock::time_point edge_first_connected_at;
size_t active_requests = 0;
uint16_t strike_count = 0;
bool rotation_in_progress = false;
std::string to_string() const;
};
struct PendingRotation {
std::string old_path_id;
PathCategory category;
OnionPath new_path;
};
inline PathCategory to_path_category(RequestCategory category) {
switch (category) {
case RequestCategory::standard: return PathCategory::standard;
case RequestCategory::standard_small: return PathCategory::standard;
case RequestCategory::file: return PathCategory::file;
case RequestCategory::file_small: return PathCategory::file;
}
return PathCategory::standard; // Should not be reached
}
class OnionRequestRouter : public IRouter, public std::enable_shared_from_this<OnionRequestRouter> {
private:
friend class TestOnionRequestRouter;
bool _ready = false;
bool _suspended = false;
config::OnionRequestRouter _config;
std::shared_ptr<oxen::quic::Loop> _loop;
std::shared_ptr<oxen::quic::Loop> _disk_loop;
std::weak_ptr<SnodePool> _snode_pool;
std::weak_ptr<ITransport> _transport;
std::vector<cached_edge_node> _cached_edge_nodes;
std::unordered_map<PathCategory, std::vector<OnionPath>> _paths;
std::unordered_map<PathCategory, std::vector<OnionPath>> _paths_pending_drop;
std::unordered_map<PathCategory, std::shared_ptr<detail::RequestQueue>> _request_queues;
std::unordered_map<std::string, std::pair<UploadRequest, std::thread>> _active_uploads;
std::unordered_map<std::string, DownloadRequest> _active_downloads;
oxen::quic::event_ptr _path_rotation_timer;
std::unordered_map<PathCategory, int> _in_progress_path_builds;
std::unordered_map<std::string, int> _path_build_retries;
std::unordered_map<
std::string,
std::pair<
std::vector<service_node>,
std::optional<std::chrono::system_clock::time_point>>>
_pending_paths;
std::multimap<std::chrono::steady_clock::time_point, std::pair<std::string, PathCategory>>
_path_rotation_schedule;
std::unordered_map<std::string, PendingRotation> _pending_rotation_paths;
// Disk I/O
std::filesystem::path _edge_node_cache_file_path;
public:
OnionRequestRouter(
config::OnionRequestRouter config,
std::shared_ptr<oxen::quic::Loop> loop,
std::shared_ptr<oxen::quic::Loop> disk_loop,
std::weak_ptr<SnodePool> snode_pool,
std::weak_ptr<ITransport> transport);
~OnionRequestRouter() override;
void suspend() override;
void resume(bool automatically_reconnect = true) override;
void close_connections() override;
void clear_cache() override;
ConnectionStatus get_status() const override { return _status.load(); };
std::vector<PathInfo> get_active_paths() override;
std::vector<service_node> get_all_used_nodes() override;
void send_request(Request request, network_response_callback_t callback) override;
void upload(UploadRequest request) override;
void download(DownloadRequest request) override;
private:
std::atomic<ConnectionStatus> _status{ConnectionStatus::unknown};
// Disk I/O functions
void _load_from_disk();
static void _clear_disk_cache(const std::filesystem::path& file_path);
static void _perform_edge_node_write(
const std::filesystem::path& file_path, std::span<const cached_edge_node> edge_nodes);
// All of the below functions should only be called from within `_loop`
void _finish_setup();
void _pre_build_paths_if_needed();
void _close_connections();
void _update_status();
void _send_request_internal(Request request, network_response_callback_t callback);
void _upload_internal(UploadRequest request);
void _download_internal(DownloadRequest request);
void _build_path(
PathCategory category,
std::optional<std::string> initiating_req_id,
const std::vector<service_node>& nodes_to_exclude,
std::optional<std::string> original_path_id = std::nullopt,
std::optional<service_node> specific_edge_node = std::nullopt,
std::optional<std::chrono::system_clock::time_point> edge_node_first_connection_at =
std::nullopt);
void _on_edge_connectivity_response(
const std::string& path_id,
PathCategory category,
std::optional<std::string> initiating_req_id,
bool success,
std::optional<uint64_t> error_code);
OnionPath* _find_valid_path(const Request& request);
void _send_on_path(OnionPath& path, Request request, network_response_callback_t callback);
void _handle_transport_response(
std::string path_id,
Request original_request,
bool success,
bool timeout,
int16_t status_code,
std::vector<std::pair<std::string, std::string>> headers,
std::optional<std::string> decrypted_body,
network_response_callback_t callback);
void _decrement_and_cleanup_path(const std::string& path_id, PathCategory category);
void _handle_path_failure(
const std::string& path_id,
const PathCategory& category,
const std::unordered_set<ed25519_pubkey>& already_penalized_nodes);
void _try_repair_path(
const std::string& path_id,
const PathCategory& category,
const ed25519_pubkey& bad_node_pubkey);
void _schedule_path_rotation(
const std::string& path_id,
PathCategory category,
std::chrono::steady_clock::time_point rotate_at);
void _check_path_rotations(std::optional<std::chrono::steady_clock::time_point> now);
void _update_rotation_timer();
void _rotate_path(const std::string& path_id, PathCategory category);
void _on_rotation_verification_response(
const std::string& new_path_id,
bool success,
bool timeout,
int16_t status_code,
std::chrono::steady_clock::time_point rotate_at);
};
} // namespace session::network