tomato/toxcore/onion_client.c
Green Sky 261d2e53b7 Squashed 'external/toxcore/c-toxcore/' changes from 55752a2e2ef..11ab1d2a723
11ab1d2a723 fix: reduce memory usage in group chats by 75% Significantly reduced the memory usage of groups since all message slots are preallocated for every peer for send and receive buffers of buffer size (hundreds of MiB peak when save contained alot of peers to try to connect to)
4f09f4e147c chore: Fix tsan build by moving it to GitHub CI.
6460c25c9e0 refactor: Use `merge_sort` instead of `qsort` for sorting.
c660bbe8c95 test: Fix crypto_test to initialise its plain text buffer.
0204db6184b cleanup: Fix layering check warnings.
df2211e1548 refactor: Use tox memory allocator for temporary buffers in crypto.
ac812871a2e feat: implement the last 2 missing network struct functions and make use of them
29d1043be0b test: friend request test now tests min/max message sizes
93aafd78c1f fix: friend requests with very long messages are no longer dropped
819aa2b2618 feat: Add option to disable DNS lookups in toxcore.
0ac23cee035 fix: windows use of REUSEADDR
7d2811d302d chore(ci): make bazel server shutdown faster
1dc399ba20d chore: Use vcpkg instead of conan in the MSVC build.
14d823165d9 chore: Migrate to conan 2.
bdd17c16787 cleanup: Allocate logger using tox memory allocator.
b396c061515 chore(deps): bump third_party/cmp from `2ac6bca` to `52bfcfa`
2e94da60d09 feat(net): add missing connect to network struct
41fb1839c7b chore: Add check to ensure version numbers agree.
934a8301113 chore: Release 0.2.20
3acef4bf044 fix: Add missing free in dht_get_nodes_response event.

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: 11ab1d2a7232eee19b51ce126ccce267d6578903
2024-12-19 16:27:40 +01:00

2273 lines
74 KiB
C

/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2016-2018 The TokTok team.
* Copyright © 2013 Tox project.
*/
/**
* Implementation of the client part of docs/Prevent_Tracking.txt (The part that
* uses the onion stuff to connect to the friend)
*/
#include "onion_client.h"
#include <assert.h>
#include <string.h>
#include "DHT.h"
#include "LAN_discovery.h"
#include "TCP_connection.h"
#include "attributes.h"
#include "ccompat.h"
#include "crypto_core.h"
#include "group_announce.h"
#include "group_onion_announce.h"
#include "logger.h"
#include "mem.h"
#include "mono_time.h"
#include "net_crypto.h"
#include "network.h"
#include "onion.h"
#include "onion_announce.h"
#include "ping_array.h"
#include "sort.h"
#include "timed_auth.h"
#include "util.h"
/** @brief defines for the array size and timeout for onion announce packets. */
#define ANNOUNCE_ARRAY_SIZE 256
#define ANNOUNCE_TIMEOUT 10
typedef struct Onion_Node {
uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE];
IP_Port ip_port;
uint8_t ping_id[ONION_PING_ID_SIZE];
uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE];
uint8_t is_stored; // Tribool.
uint64_t added_time;
uint64_t timestamp;
uint64_t last_pinged;
uint8_t pings_since_last_response;
uint32_t path_used;
} Onion_Node;
typedef struct Onion_Client_Paths {
Onion_Path paths[NUMBER_ONION_PATHS];
uint64_t last_path_success[NUMBER_ONION_PATHS];
uint64_t last_path_used[NUMBER_ONION_PATHS];
uint64_t path_creation_time[NUMBER_ONION_PATHS];
/* number of times used without success. */
unsigned int last_path_used_times[NUMBER_ONION_PATHS];
} Onion_Client_Paths;
typedef struct Last_Pinged {
uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE];
uint64_t timestamp;
} Last_Pinged;
struct Onion_Friend {
bool is_valid;
bool is_online;
bool know_dht_public_key;
uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE];
uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE];
Onion_Node clients_list[MAX_ONION_CLIENTS];
uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE];
uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE];
uint64_t last_dht_pk_onion_sent;
uint64_t last_dht_pk_dht_sent;
uint64_t last_noreplay;
uint64_t last_populated; // the last time we had a fully populated client nodes list
uint64_t time_last_pinged; // the last time we pinged this friend with any node
uint32_t run_count;
uint32_t pings; // how many sucessful pings we've made for this friend
Last_Pinged last_pinged[MAX_STORED_PINGED_NODES];
uint8_t last_pinged_index;
recv_tcp_relay_cb *tcp_relay_node_callback;
void *tcp_relay_node_callback_object;
uint32_t tcp_relay_node_callback_number;
onion_dht_pk_cb *dht_pk_callback;
void *dht_pk_callback_object;
uint32_t dht_pk_callback_number;
uint8_t gc_data[GCA_MAX_DATA_LENGTH];
uint8_t gc_public_key[ENC_PUBLIC_KEY_SIZE];
uint16_t gc_data_length;
bool is_groupchat;
};
static const Onion_Friend empty_onion_friend = {false};
typedef struct Onion_Data_Handler {
oniondata_handler_cb *function;
void *object;
} Onion_Data_Handler;
struct Onion_Client {
const Mono_Time *mono_time;
const Logger *logger;
const Random *rng;
const Memory *mem;
DHT *dht;
Net_Crypto *c;
Networking_Core *net;
Onion_Friend *friends_list;
uint16_t num_friends;
Onion_Node clients_announce_list[MAX_ONION_CLIENTS_ANNOUNCE];
uint64_t last_announce;
Onion_Client_Paths onion_paths_self;
Onion_Client_Paths onion_paths_friends;
uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE];
uint64_t last_run;
uint64_t first_run;
uint64_t last_time_connected;
uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE];
uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE];
Last_Pinged last_pinged[MAX_STORED_PINGED_NODES];
Node_format path_nodes[MAX_PATH_NODES];
uint16_t path_nodes_index;
Node_format path_nodes_bs[MAX_PATH_NODES];
uint16_t path_nodes_index_bs;
Ping_Array *announce_ping_array;
uint8_t last_pinged_index;
Onion_Data_Handler onion_data_handlers[256];
uint64_t last_packet_recv;
uint64_t last_populated; // the last time we had a fully populated path nodes list
unsigned int onion_connected;
bool udp_connected;
onion_group_announce_cb *group_announce_response;
void *group_announce_response_user_data;
};
uint16_t onion_get_friend_count(const Onion_Client *const onion_c)
{
return onion_c->num_friends;
}
Onion_Friend *onion_get_friend(const Onion_Client *const onion_c, uint16_t friend_num)
{
return &onion_c->friends_list[friend_num];
}
const uint8_t *onion_friend_get_gc_public_key(const Onion_Friend *const onion_friend)
{
return onion_friend->gc_public_key;
}
const uint8_t *onion_friend_get_gc_public_key_num(const Onion_Client *const onion_c, uint32_t num)
{
return onion_c->friends_list[num].gc_public_key;
}
void onion_friend_set_gc_public_key(Onion_Friend *const onion_friend, const uint8_t *public_key)
{
memcpy(onion_friend->gc_public_key, public_key, ENC_PUBLIC_KEY_SIZE);
}
void onion_friend_set_gc_data(Onion_Friend *const onion_friend, const uint8_t *gc_data, uint16_t gc_data_length)
{
if (gc_data_length > 0 && gc_data != nullptr) {
memcpy(onion_friend->gc_data, gc_data, gc_data_length);
}
onion_friend->gc_data_length = gc_data_length;
onion_friend->is_groupchat = true;
}
bool onion_friend_is_groupchat(const Onion_Friend *const onion_friend)
{
return onion_friend->is_groupchat;
}
DHT *onion_get_dht(const Onion_Client *onion_c)
{
return onion_c->dht;
}
Net_Crypto *onion_get_net_crypto(const Onion_Client *onion_c)
{
return onion_c->c;
}
/** @brief Add a node to the path_nodes bootstrap array.
*
* If a node with the given public key was already in the bootstrap array, this function has no
* effect and returns successfully. There is currently no way to update the IP/port for a bootstrap
* node, so if it changes, the Onion_Client must be recreated.
*
* @param onion_c The onion client object.
* @param ip_port IP/port for the bootstrap node.
* @param public_key DHT public key for the bootstrap node.
*
* @retval false on failure
* @retval true on success
*/
bool onion_add_bs_path_node(Onion_Client *onion_c, const IP_Port *ip_port, const uint8_t *public_key)
{
if (!net_family_is_ipv4(ip_port->ip.family) && !net_family_is_ipv6(ip_port->ip.family)) {
return false;
}
for (unsigned int i = 0; i < MAX_PATH_NODES; ++i) {
if (pk_equal(public_key, onion_c->path_nodes_bs[i].public_key)) {
return true;
}
}
onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].ip_port = *ip_port;
memcpy(onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].public_key, public_key,
CRYPTO_PUBLIC_KEY_SIZE);
const uint16_t last = onion_c->path_nodes_index_bs;
++onion_c->path_nodes_index_bs;
if (onion_c->path_nodes_index_bs < last) {
onion_c->path_nodes_index_bs = MAX_PATH_NODES + 1;
}
return true;
}
/** @brief Add a node to the path_nodes array.
*
* return -1 on failure
* return 0 on success
*/
non_null()
static int onion_add_path_node(Onion_Client *onion_c, const IP_Port *ip_port, const uint8_t *public_key)
{
if (!net_family_is_ipv4(ip_port->ip.family) && !net_family_is_ipv6(ip_port->ip.family)) {
return -1;
}
for (unsigned int i = 0; i < MAX_PATH_NODES; ++i) {
if (pk_equal(public_key, onion_c->path_nodes[i].public_key)) {
return -1;
}
}
onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].ip_port = *ip_port;
memcpy(onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].public_key, public_key,
CRYPTO_PUBLIC_KEY_SIZE);
const uint16_t last = onion_c->path_nodes_index;
++onion_c->path_nodes_index;
if (onion_c->path_nodes_index < last) {
onion_c->path_nodes_index = MAX_PATH_NODES + 1;
}
return 0;
}
/** @brief Put up to max_num nodes in nodes.
*
* return the number of nodes.
*/
uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num)
{
if (max_num == 0) {
return 0;
}
const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES);
uint16_t i = 0;
while (i < max_num && i < num_nodes) {
nodes[i] = onion_c->path_nodes[(onion_c->path_nodes_index - (1 + i)) % num_nodes];
++i;
}
for (uint16_t j = 0; i < max_num && j < MAX_PATH_NODES && j < onion_c->path_nodes_index_bs; ++j) {
bool already_saved = false;
for (uint16_t k = 0; k < num_nodes; ++k) {
if (pk_equal(nodes[k].public_key, onion_c->path_nodes_bs[j].public_key)) {
already_saved = true;
break;
}
}
if (!already_saved) {
nodes[i] = onion_c->path_nodes_bs[j];
++i;
}
}
return i;
}
/** @brief Put up to max_num random nodes in nodes.
*
* return the number of nodes.
*/
non_null()
static uint16_t random_nodes_path_onion(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num)
{
if (max_num == 0) {
return 0;
}
const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES);
// if (dht_non_lan_connected(onion_c->dht)) {
if (dht_isconnected(onion_c->dht)) {
if (num_nodes == 0) {
return 0;
}
for (unsigned int i = 0; i < max_num; ++i) {
const uint32_t rand_idx = random_range_u32(onion_c->rng, num_nodes);
nodes[i] = onion_c->path_nodes[rand_idx];
}
} else {
const int random_tcp = get_random_tcp_con_number(onion_c->c);
if (random_tcp == -1) {
return 0;
}
if (num_nodes >= 2) {
nodes[0] = empty_node_format;
nodes[0].ip_port = tcp_connections_number_to_ip_port(random_tcp);
for (unsigned int i = 1; i < max_num; ++i) {
const uint32_t rand_idx = random_range_u32(onion_c->rng, num_nodes);
nodes[i] = onion_c->path_nodes[rand_idx];
}
} else {
const uint16_t num_nodes_bs = min_u16(onion_c->path_nodes_index_bs, MAX_PATH_NODES);
if (num_nodes_bs == 0) {
return 0;
}
nodes[0] = empty_node_format;
nodes[0].ip_port = tcp_connections_number_to_ip_port(random_tcp);
for (unsigned int i = 1; i < max_num; ++i) {
const uint32_t rand_idx = random_range_u32(onion_c->rng, num_nodes_bs);
nodes[i] = onion_c->path_nodes_bs[rand_idx];
}
}
}
return max_num;
}
/**
* return -1 if nodes are suitable for creating a new path.
* return path number of already existing similar path if one already exists.
*/
non_null()
static int is_path_used(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, const Node_format *nodes)
{
for (unsigned int i = 0; i < NUMBER_ONION_PATHS; ++i) {
if (mono_time_is_timeout(mono_time, onion_paths->last_path_success[i], ONION_PATH_TIMEOUT)) {
continue;
}
if (mono_time_is_timeout(mono_time, onion_paths->path_creation_time[i], ONION_PATH_MAX_LIFETIME)) {
continue;
}
// TODO(irungentoo): do we really have to check it with the last node?
if (ipport_equal(&onion_paths->paths[i].ip_port1, &nodes[ONION_PATH_LENGTH - 1].ip_port)) {
return i;
}
}
return -1;
}
/** is path timed out */
non_null()
static bool path_timed_out(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, uint32_t pathnum)
{
pathnum = pathnum % NUMBER_ONION_PATHS;
const bool is_new = onion_paths->last_path_success[pathnum] == onion_paths->path_creation_time[pathnum];
const uint64_t timeout = is_new ? ONION_PATH_FIRST_TIMEOUT : ONION_PATH_TIMEOUT;
return (onion_paths->last_path_used_times[pathnum] >= ONION_PATH_MAX_NO_RESPONSE_USES
&& mono_time_is_timeout(mono_time, onion_paths->last_path_used[pathnum], timeout))
|| mono_time_is_timeout(mono_time, onion_paths->path_creation_time[pathnum], ONION_PATH_MAX_LIFETIME);
}
/** should node be considered to have timed out */
non_null()
static bool onion_node_timed_out(const Onion_Node *node, const Mono_Time *mono_time)
{
return node->timestamp == 0
|| (node->pings_since_last_response >= ONION_NODE_MAX_PINGS
&& mono_time_is_timeout(mono_time, node->last_pinged, ONION_NODE_TIMEOUT));
}
/** @brief Create a new path or use an old suitable one (if pathnum is valid)
* or a random one from onion_paths.
*
* return -1 on failure
* return 0 on success
*
* TODO(irungentoo): Make this function better, it currently probably is
* vulnerable to some attacks that could deanonimize us.
*/
non_null()
static int random_path(const Onion_Client *onion_c, Onion_Client_Paths *onion_paths, uint32_t pathnum, Onion_Path *path)
{
if (pathnum == UINT32_MAX) {
pathnum = random_range_u32(onion_c->rng, NUMBER_ONION_PATHS);
} else {
pathnum = pathnum % NUMBER_ONION_PATHS;
}
if (path_timed_out(onion_c->mono_time, onion_paths, pathnum)) {
Node_format nodes[ONION_PATH_LENGTH];
if (random_nodes_path_onion(onion_c, nodes, ONION_PATH_LENGTH) != ONION_PATH_LENGTH) {
return -1;
}
const int n = is_path_used(onion_c->mono_time, onion_paths, nodes);
if (n == -1) {
if (create_onion_path(onion_c->rng, onion_c->dht, &onion_paths->paths[pathnum], nodes) == -1) {
return -1;
}
onion_paths->path_creation_time[pathnum] = mono_time_get(onion_c->mono_time);
onion_paths->last_path_success[pathnum] = onion_paths->path_creation_time[pathnum];
onion_paths->last_path_used_times[pathnum] = ONION_PATH_MAX_NO_RESPONSE_USES / 2;
uint32_t path_num = random_u32(onion_c->rng);
path_num /= NUMBER_ONION_PATHS;
path_num *= NUMBER_ONION_PATHS;
path_num += pathnum;
onion_paths->paths[pathnum].path_num = path_num;
} else {
pathnum = n;
}
}
if (onion_paths->last_path_used_times[pathnum] < ONION_PATH_MAX_NO_RESPONSE_USES) {
onion_paths->last_path_used[pathnum] = mono_time_get(onion_c->mono_time);
}
++onion_paths->last_path_used_times[pathnum];
*path = onion_paths->paths[pathnum];
return 0;
}
/** Set path timeouts, return the path number. */
non_null()
static uint32_t set_path_timeouts(Onion_Client *onion_c, uint32_t num, uint32_t path_num)
{
if (num > onion_c->num_friends) {
return -1;
}
Onion_Client_Paths *onion_paths;
if (num == 0) {
onion_paths = &onion_c->onion_paths_self;
} else {
onion_paths = &onion_c->onion_paths_friends;
}
if (onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num) {
onion_paths->last_path_success[path_num % NUMBER_ONION_PATHS] = mono_time_get(onion_c->mono_time);
onion_paths->last_path_used_times[path_num % NUMBER_ONION_PATHS] = 0;
Node_format nodes[ONION_PATH_LENGTH];
if (onion_path_to_nodes(nodes, ONION_PATH_LENGTH, &onion_paths->paths[path_num % NUMBER_ONION_PATHS]) == 0) {
for (unsigned int i = 0; i < ONION_PATH_LENGTH; ++i) {
onion_add_path_node(onion_c, &nodes[i].ip_port, nodes[i].public_key);
}
}
return path_num;
}
return -1;
}
/** @brief Function to send onion packet via TCP and UDP.
*
* return -1 on failure.
* return 0 on success.
*/
non_null()
static int send_onion_packet_tcp_udp(const Onion_Client *onion_c, const Onion_Path *path, const IP_Port *dest,
const uint8_t *data, uint16_t length)
{
if (net_family_is_ipv4(path->ip_port1.ip.family) || net_family_is_ipv6(path->ip_port1.ip.family)) {
uint8_t packet[ONION_MAX_PACKET_SIZE];
const int len = create_onion_packet(onion_c->mem, onion_c->rng, packet, sizeof(packet), path, dest, data, length);
if (len == -1) {
return -1;
}
if (sendpacket(onion_c->net, &path->ip_port1, packet, len) != len) {
return -1;
}
return 0;
}
unsigned int tcp_connections_number;
if (ip_port_to_tcp_connections_number(&path->ip_port1, &tcp_connections_number)) {
uint8_t packet[ONION_MAX_PACKET_SIZE];
const int len = create_onion_packet_tcp(onion_c->mem, onion_c->rng, packet, sizeof(packet), path, dest, data, length);
if (len == -1) {
return -1;
}
return send_tcp_onion_request(onion_c->c, tcp_connections_number, packet, len);
}
return -1;
}
/** @brief Creates a sendback for use in an announce request.
*
* num is 0 if we used our secret public key for the announce
* num is 1 + friendnum if we use a temporary one.
*
* Public key is the key we will be sending it to.
* ip_port is the ip_port of the node we will be sending
* it to.
*
* sendback must be at least ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big
*
* return -1 on failure
* return 0 on success
*
*/
non_null()
static int new_sendback(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, const IP_Port *ip_port,
uint32_t path_num, uint64_t *sendback)
{
uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)];
memcpy(data, &num, sizeof(uint32_t));
memcpy(data + sizeof(uint32_t), public_key, CRYPTO_PUBLIC_KEY_SIZE);
memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, ip_port, sizeof(IP_Port));
memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), &path_num, sizeof(uint32_t));
*sendback = ping_array_add(onion_c->announce_ping_array, onion_c->mono_time, onion_c->rng, data, sizeof(data));
if (*sendback == 0) {
LOGGER_TRACE(onion_c->logger, "generating sendback in announce ping array failed");
return -1;
}
return 0;
}
/** @brief Checks if the sendback is valid and returns the public key contained in it in ret_pubkey and the
* ip contained in it in ret_ip_port
*
* sendback is the sendback ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big
* ret_pubkey must be at least CRYPTO_PUBLIC_KEY_SIZE big
* ret_ip_port must be at least 1 big
*
* return -1 on failure
* return num (see new_sendback(...)) on success
*/
non_null()
static uint32_t check_sendback(Onion_Client *onion_c, const uint8_t *sendback, uint8_t *ret_pubkey,
IP_Port *ret_ip_port, uint32_t *path_num)
{
uint64_t sback;
memcpy(&sback, sendback, sizeof(uint64_t));
uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)];
if (ping_array_check(onion_c->announce_ping_array, onion_c->mono_time, data, sizeof(data), sback) != sizeof(data)) {
return -1;
}
memcpy(ret_pubkey, data + sizeof(uint32_t), CRYPTO_PUBLIC_KEY_SIZE);
memcpy(ret_ip_port, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port));
memcpy(path_num, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), sizeof(uint32_t));
uint32_t num;
memcpy(&num, data, sizeof(uint32_t));
return num;
}
non_null(1, 3, 4) nullable(5)
static int client_send_announce_request(Onion_Client *onion_c, uint32_t num, const IP_Port *dest,
const uint8_t *dest_pubkey, const uint8_t *ping_id, uint32_t pathnum)
{
if (num > onion_c->num_friends) {
LOGGER_TRACE(onion_c->logger, "not sending announce to out of bounds friend %u (num friends: %u)", num, onion_c->num_friends);
return -1;
}
uint64_t sendback;
Onion_Path path;
if (num == 0) {
if (random_path(onion_c, &onion_c->onion_paths_self, pathnum, &path) == -1) {
LOGGER_TRACE(onion_c->logger, "cannot find path to self");
return -1;
}
} else {
if (random_path(onion_c, &onion_c->onion_paths_friends, pathnum, &path) == -1) {
LOGGER_TRACE(onion_c->logger, "cannot find path to friend");
return -1;
}
}
if (new_sendback(onion_c, num, dest_pubkey, dest, path.path_num, &sendback) == -1) {
return -1;
}
uint8_t zero_ping_id[ONION_PING_ID_SIZE] = {0};
if (ping_id == nullptr) {
ping_id = zero_ping_id;
}
uint8_t request[ONION_ANNOUNCE_REQUEST_MAX_SIZE];
int len;
if (num == 0) {
len = create_announce_request(
onion_c->mem, onion_c->rng, request, sizeof(request), dest_pubkey, nc_get_self_public_key(onion_c->c),
nc_get_self_secret_key(onion_c->c), ping_id, nc_get_self_public_key(onion_c->c),
onion_c->temp_public_key, sendback);
} else {
Onion_Friend *onion_friend = &onion_c->friends_list[num - 1];
if (onion_friend->gc_data_length == 0) { // contact is a friend
len = create_announce_request(
onion_c->mem, onion_c->rng, request, sizeof(request), dest_pubkey, onion_friend->temp_public_key,
onion_friend->temp_secret_key, ping_id, onion_friend->real_public_key,
zero_ping_id, sendback);
} else { // contact is a gc
onion_friend->is_groupchat = true;
len = create_gca_announce_request(
onion_c->mem, onion_c->rng, request, sizeof(request), dest_pubkey, onion_friend->temp_public_key,
onion_friend->temp_secret_key, ping_id, onion_friend->real_public_key,
zero_ping_id, sendback, onion_friend->gc_data,
onion_friend->gc_data_length);
}
}
if (len == -1) {
LOGGER_TRACE(onion_c->logger, "failed to create announce request");
return -1;
}
Ip_Ntoa ip_str;
LOGGER_TRACE(onion_c->logger, "sending onion packet to %s:%d (%02x, %d bytes)",
net_ip_ntoa(&dest->ip, &ip_str), net_ntohs(dest->port), request[0], len);
return send_onion_packet_tcp_udp(onion_c, &path, dest, request, len);
}
typedef struct Onion_Node_Cmp {
const Memory *mem;
const Mono_Time *mono_time;
const uint8_t *comp_public_key;
} Onion_Node_Cmp;
non_null()
static int onion_node_cmp(const Onion_Node_Cmp *cmp, const Onion_Node *entry1, const Onion_Node *entry2)
{
const bool t1 = onion_node_timed_out(entry1, cmp->mono_time);
const bool t2 = onion_node_timed_out(entry2, cmp->mono_time);
if (t1 && t2) {
return 0;
}
if (t1) {
return -1;
}
if (t2) {
return 1;
}
const int closest = id_closest(cmp->comp_public_key, entry1->public_key, entry2->public_key);
if (closest == 1) {
return 1;
}
if (closest == 2) {
return -1;
}
return 0;
}
non_null()
static bool onion_node_less_handler(const void *object, const void *a, const void *b)
{
const Onion_Node_Cmp *cmp = (const Onion_Node_Cmp *)object;
const Onion_Node *entry1 = (const Onion_Node *)a;
const Onion_Node *entry2 = (const Onion_Node *)b;
return onion_node_cmp(cmp, entry1, entry2) < 0;
}
non_null()
static const void *onion_node_get_handler(const void *arr, uint32_t index)
{
const Onion_Node *entries = (const Onion_Node *)arr;
return &entries[index];
}
non_null()
static void onion_node_set_handler(void *arr, uint32_t index, const void *val)
{
Onion_Node *entries = (Onion_Node *)arr;
const Onion_Node *entry = (const Onion_Node *)val;
entries[index] = *entry;
}
non_null()
static void *onion_node_subarr_handler(void *arr, uint32_t index, uint32_t size)
{
Onion_Node *entries = (Onion_Node *)arr;
return &entries[index];
}
non_null()
static void *onion_node_alloc_handler(const void *object, uint32_t size)
{
const Onion_Node_Cmp *cmp = (const Onion_Node_Cmp *)object;
Onion_Node *tmp = (Onion_Node *)mem_valloc(cmp->mem, size, sizeof(Onion_Node));
if (tmp == nullptr) {
return nullptr;
}
return tmp;
}
non_null()
static void onion_node_delete_handler(const void *object, void *arr, uint32_t size)
{
const Onion_Node_Cmp *cmp = (const Onion_Node_Cmp *)object;
mem_delete(cmp->mem, arr);
}
static const Sort_Funcs onion_node_cmp_funcs = {
onion_node_less_handler,
onion_node_get_handler,
onion_node_set_handler,
onion_node_subarr_handler,
onion_node_alloc_handler,
onion_node_delete_handler,
};
non_null()
static void sort_onion_node_list(const Memory *mem, const Mono_Time *mono_time,
Onion_Node *list, unsigned int length, const uint8_t *comp_public_key)
{
// Pass comp_public_key to sort with each Onion_Node entry, so the
// comparison function can use it as the base of comparison.
const Onion_Node_Cmp cmp = {
mem,
mono_time,
comp_public_key,
};
merge_sort(list, length, &cmp, &onion_node_cmp_funcs);
}
non_null()
static int client_add_to_list(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, const IP_Port *ip_port,
uint8_t is_stored, const uint8_t *pingid_or_key, uint32_t path_used)
{
if (num > onion_c->num_friends) {
return -1;
}
Onion_Node *node_list = nullptr;
const uint8_t *reference_id = nullptr;
unsigned int list_length;
if (num == 0) {
node_list = onion_c->clients_announce_list;
reference_id = nc_get_self_public_key(onion_c->c);
list_length = MAX_ONION_CLIENTS_ANNOUNCE;
if (is_stored == 1 && !pk_equal(pingid_or_key, onion_c->temp_public_key)) {
is_stored = 0;
}
} else {
if (is_stored >= 2) {
return -1;
}
node_list = onion_c->friends_list[num - 1].clients_list;
reference_id = onion_c->friends_list[num - 1].real_public_key;
list_length = MAX_ONION_CLIENTS;
}
sort_onion_node_list(onion_c->mem, onion_c->mono_time, node_list, list_length, reference_id);
int index = -1;
bool stored = false;
if (onion_node_timed_out(&node_list[0], onion_c->mono_time)
|| id_closest(reference_id, node_list[0].public_key, public_key) == 2) {
index = 0;
}
for (unsigned int i = 0; i < list_length; ++i) {
if (pk_equal(node_list[i].public_key, public_key)) {
index = i;
stored = true;
break;
}
}
if (index == -1) {
return 0;
}
memcpy(node_list[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE);
node_list[index].ip_port = *ip_port;
// TODO(irungentoo): remove this and find a better source of nodes to use for paths.
onion_add_path_node(onion_c, ip_port, public_key);
if (is_stored == 1) {
memcpy(node_list[index].data_public_key, pingid_or_key, CRYPTO_PUBLIC_KEY_SIZE);
} else {
memcpy(node_list[index].ping_id, pingid_or_key, ONION_PING_ID_SIZE);
}
node_list[index].is_stored = is_stored;
node_list[index].timestamp = mono_time_get(onion_c->mono_time);
node_list[index].pings_since_last_response = 0;
if (!stored) {
node_list[index].last_pinged = 0;
node_list[index].added_time = mono_time_get(onion_c->mono_time);
}
node_list[index].path_used = path_used;
return 0;
}
non_null()
static bool good_to_ping(const Mono_Time *mono_time, Last_Pinged *last_pinged, uint8_t *last_pinged_index,
const uint8_t *public_key)
{
for (unsigned int i = 0; i < MAX_STORED_PINGED_NODES; ++i) {
if (!mono_time_is_timeout(mono_time, last_pinged[i].timestamp, MIN_NODE_PING_TIME)) {
if (pk_equal(last_pinged[i].public_key, public_key)) {
return false;
}
}
}
memcpy(last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE);
last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].timestamp = mono_time_get(mono_time);
++*last_pinged_index;
return true;
}
non_null()
static int client_ping_nodes(Onion_Client *onion_c, uint32_t num, const Node_format *nodes, uint16_t num_nodes,
const IP_Port *source)
{
if (num > onion_c->num_friends) {
return -1;
}
if (num_nodes == 0) {
return 0;
}
const Onion_Node *node_list = nullptr;
const uint8_t *reference_id = nullptr;
unsigned int list_length;
Last_Pinged *last_pinged = nullptr;
uint8_t *last_pinged_index = nullptr;
if (num == 0) {
node_list = onion_c->clients_announce_list;
reference_id = nc_get_self_public_key(onion_c->c);
list_length = MAX_ONION_CLIENTS_ANNOUNCE;
last_pinged = onion_c->last_pinged;
last_pinged_index = &onion_c->last_pinged_index;
} else {
node_list = onion_c->friends_list[num - 1].clients_list;
reference_id = onion_c->friends_list[num - 1].real_public_key;
list_length = MAX_ONION_CLIENTS;
last_pinged = onion_c->friends_list[num - 1].last_pinged;
last_pinged_index = &onion_c->friends_list[num - 1].last_pinged_index;
}
const bool lan_ips_accepted = ip_is_lan(&source->ip);
for (uint32_t i = 0; i < num_nodes; ++i) {
if (!lan_ips_accepted) {
if (ip_is_lan(&nodes[i].ip_port.ip)) {
continue;
}
}
if (onion_node_timed_out(&node_list[0], onion_c->mono_time)
|| id_closest(reference_id, node_list[0].public_key, nodes[i].public_key) == 2
|| onion_node_timed_out(&node_list[1], onion_c->mono_time)
|| id_closest(reference_id, node_list[1].public_key, nodes[i].public_key) == 2) {
uint32_t j;
/* check if node is already in list. */
for (j = 0; j < list_length; ++j) {
if (pk_equal(node_list[j].public_key, nodes[i].public_key)) {
break;
}
}
if (j == list_length && good_to_ping(onion_c->mono_time, last_pinged, last_pinged_index, nodes[i].public_key)) {
client_send_announce_request(onion_c, num, &nodes[i].ip_port, nodes[i].public_key, nullptr, -1);
}
}
}
return 0;
}
non_null()
static bool handle_group_announce_response(Onion_Client *onion_c, uint32_t num, const uint8_t *plain, size_t plain_size)
{
if (onion_c->group_announce_response == nullptr) {
return true;
}
return onion_c->group_announce_response(onion_c, num, plain, plain_size, onion_c->group_announce_response_user_data);
}
non_null(1, 2, 3) nullable(5)
static int handle_announce_response(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length,
void *userdata)
{
Onion_Client *onion_c = (Onion_Client *)object;
if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) {
LOGGER_TRACE(onion_c->logger, "invalid announce response length: %u (min: %u, max: %u)",
length, (unsigned int)ONION_ANNOUNCE_RESPONSE_MIN_SIZE, (unsigned int)ONION_ANNOUNCE_RESPONSE_MAX_SIZE);
return 1;
}
uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE];
IP_Port ip_port;
uint32_t path_num;
const uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num);
if (num > onion_c->num_friends) {
return 1;
}
uint8_t plain[1 + ONION_PING_ID_SIZE + ONION_ANNOUNCE_RESPONSE_MAX_SIZE - ONION_ANNOUNCE_RESPONSE_MIN_SIZE];
const uint32_t plain_size = 1 + ONION_PING_ID_SIZE + length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE;
int len;
const uint16_t nonce_start = 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH;
const uint16_t ciphertext_start = nonce_start + CRYPTO_NONCE_SIZE;
const uint16_t ciphertext_size = length - ciphertext_start;
if (num == 0) {
len = decrypt_data(onion_c->mem, public_key, nc_get_self_secret_key(onion_c->c),
&packet[nonce_start], &packet[ciphertext_start], ciphertext_size, plain);
} else {
if (!onion_c->friends_list[num - 1].is_valid) {
LOGGER_TRACE(onion_c->logger, "friend number %lu is invalid", (unsigned long)(num - 1));
return 1;
}
len = decrypt_data(onion_c->mem, public_key, onion_c->friends_list[num - 1].temp_secret_key,
&packet[nonce_start], &packet[ciphertext_start], ciphertext_size, plain);
}
if (len < 0) {
// This happens a lot, so don't log it.
return 1;
}
if ((uint32_t)len != plain_size) {
LOGGER_WARNING(onion_c->logger, "decrypted size (%lu) is not the expected plain text size (%lu)", (unsigned long)len, (unsigned long)plain_size);
return 1;
}
const uint32_t path_used = set_path_timeouts(onion_c, num, path_num);
if (client_add_to_list(onion_c, num, public_key, &ip_port, plain[0], plain + 1, path_used) == -1) {
LOGGER_WARNING(onion_c->logger, "failed to add client to list");
return 1;
}
uint16_t len_nodes = 0;
const uint8_t nodes_count = plain[1 + ONION_PING_ID_SIZE];
if (nodes_count > 0) {
if (nodes_count > MAX_SENT_NODES) {
return 1;
}
Node_format nodes[MAX_SENT_NODES];
const int num_nodes = unpack_nodes(nodes, nodes_count, &len_nodes, plain + 2 + ONION_PING_ID_SIZE,
plain_size - 2 - ONION_PING_ID_SIZE, false);
if (num_nodes < 0) {
LOGGER_WARNING(onion_c->logger, "no nodes to unpack in onion response");
return 1;
}
if (client_ping_nodes(onion_c, num, nodes, num_nodes, source) == -1) {
LOGGER_WARNING(onion_c->logger, "pinging %d nodes failed", num_nodes);
return 1;
}
}
if (len_nodes + 1 < length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE) {
const uint16_t offset = 2 + ONION_PING_ID_SIZE + len_nodes;
if (plain_size < offset) {
return 1;
}
if (!handle_group_announce_response(onion_c, num, plain + offset, plain_size - offset)) {
return 1;
}
}
// TODO(irungentoo): LAN vs non LAN ips?, if we are connected only to LAN, are we offline?
onion_c->last_packet_recv = mono_time_get(onion_c->mono_time);
LOGGER_TRACE(onion_c->logger, "onion has received a packet at %llu",
(unsigned long long)onion_c->last_packet_recv);
return 0;
}
/* TODO(jfreegman): DEPRECATE */
non_null(1, 2, 3) nullable(5)
static int handle_announce_response_old(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length,
void *userdata)
{
Onion_Client *onion_c = (Onion_Client *)object;
if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) {
LOGGER_TRACE(onion_c->logger, "invalid announce response length: %u (min: %u, max: %u)",
length, (unsigned int)ONION_ANNOUNCE_RESPONSE_MIN_SIZE, (unsigned int)ONION_ANNOUNCE_RESPONSE_MAX_SIZE);
return 1;
}
const uint16_t len_nodes = length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE;
uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE];
IP_Port ip_port;
uint32_t path_num;
const uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num);
if (num > onion_c->num_friends) {
return 1;
}
const uint16_t plain_size = 1 + ONION_PING_ID_SIZE + len_nodes;
VLA(uint8_t, plain, plain_size);
int len;
const uint16_t nonce_start = 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH;
const uint16_t ciphertext_start = nonce_start + CRYPTO_NONCE_SIZE;
const uint16_t ciphertext_size = length - ciphertext_start;
if (num == 0) {
len = decrypt_data(onion_c->mem, public_key, nc_get_self_secret_key(onion_c->c),
&packet[nonce_start], &packet[ciphertext_start], ciphertext_size, plain);
} else {
if (!onion_c->friends_list[num - 1].is_valid) {
LOGGER_TRACE(onion_c->logger, "friend number %lu is invalid", (unsigned long)(num - 1));
return 1;
}
len = decrypt_data(onion_c->mem, public_key, onion_c->friends_list[num - 1].temp_secret_key,
&packet[nonce_start], &packet[ciphertext_start], ciphertext_size, plain);
}
if (len < 0) {
// This happens a lot, so don't log it.
return 1;
}
if ((uint32_t)len != plain_size) {
LOGGER_WARNING(onion_c->logger, "decrypted size (%lu) is not the expected plain text size (%u)", (unsigned long)len, plain_size);
return 1;
}
const uint32_t path_used = set_path_timeouts(onion_c, num, path_num);
if (client_add_to_list(onion_c, num, public_key, &ip_port, plain[0], plain + 1, path_used) == -1) {
LOGGER_WARNING(onion_c->logger, "failed to add client to list");
return 1;
}
if (len_nodes != 0) {
Node_format nodes[MAX_SENT_NODES];
const int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, plain + 1 + ONION_PING_ID_SIZE, len_nodes, false);
if (num_nodes <= 0) {
LOGGER_WARNING(onion_c->logger, "no nodes to unpack in onion response");
return 1;
}
if (client_ping_nodes(onion_c, num, nodes, num_nodes, source) == -1) {
LOGGER_WARNING(onion_c->logger, "pinging %d nodes failed", num_nodes);
return 1;
}
}
// TODO(irungentoo): LAN vs non LAN ips?, if we are connected only to LAN, are we offline?
onion_c->last_packet_recv = mono_time_get(onion_c->mono_time);
LOGGER_TRACE(onion_c->logger, "onion has received a packet at %llu",
(unsigned long long)onion_c->last_packet_recv);
return 0;
}
#define DATA_IN_RESPONSE_MIN_SIZE ONION_DATA_IN_RESPONSE_MIN_SIZE
non_null()
static int handle_data_response(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length,
void *userdata)
{
Onion_Client *onion_c = (Onion_Client *)object;
if (length <= (ONION_DATA_RESPONSE_MIN_SIZE + DATA_IN_RESPONSE_MIN_SIZE)) {
return 1;
}
if (length > MAX_DATA_REQUEST_SIZE) {
return 1;
}
const uint16_t temp_plain_size = length - ONION_DATA_RESPONSE_MIN_SIZE;
VLA(uint8_t, temp_plain, temp_plain_size);
int len = decrypt_data(onion_c->mem, packet + 1 + CRYPTO_NONCE_SIZE, onion_c->temp_secret_key, packet + 1,
packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE,
length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), temp_plain);
if ((uint32_t)len != temp_plain_size) {
return 1;
}
const uint16_t plain_size = temp_plain_size - DATA_IN_RESPONSE_MIN_SIZE;
VLA(uint8_t, plain, plain_size);
len = decrypt_data(onion_c->mem, temp_plain, nc_get_self_secret_key(onion_c->c),
packet + 1, temp_plain + CRYPTO_PUBLIC_KEY_SIZE,
temp_plain_size - CRYPTO_PUBLIC_KEY_SIZE, plain);
if ((uint32_t)len != plain_size) {
return 1;
}
if (onion_c->onion_data_handlers[plain[0]].function == nullptr) {
return 1;
}
return onion_c->onion_data_handlers[plain[0]].function(onion_c->onion_data_handlers[plain[0]].object, temp_plain, plain,
plain_size, userdata);
}
#define DHTPK_DATA_MIN_LENGTH (1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE)
#define DHTPK_DATA_MAX_LENGTH (DHTPK_DATA_MIN_LENGTH + sizeof(Node_format)*MAX_SENT_NODES)
non_null(1, 2, 3) nullable(5)
static int handle_dhtpk_announce(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t length,
void *userdata)
{
Onion_Client *onion_c = (Onion_Client *)object;
if (length < DHTPK_DATA_MIN_LENGTH) {
return 1;
}
if (length > DHTPK_DATA_MAX_LENGTH) {
return 1;
}
const int friend_num = onion_friend_num(onion_c, source_pubkey);
if (friend_num == -1) {
return 1;
}
uint64_t no_replay;
net_unpack_u64(data + 1, &no_replay);
if (no_replay <= onion_c->friends_list[friend_num].last_noreplay) {
return 1;
}
onion_c->friends_list[friend_num].last_noreplay = no_replay;
if (onion_c->friends_list[friend_num].dht_pk_callback != nullptr) {
onion_c->friends_list[friend_num].dht_pk_callback(onion_c->friends_list[friend_num].dht_pk_callback_object,
onion_c->friends_list[friend_num].dht_pk_callback_number, data + 1 + sizeof(uint64_t), userdata);
}
onion_set_friend_dht_pubkey(onion_c, friend_num, data + 1 + sizeof(uint64_t));
const uint16_t len_nodes = length - DHTPK_DATA_MIN_LENGTH;
if (len_nodes != 0) {
Node_format nodes[MAX_SENT_NODES];
const int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, data + 1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE,
len_nodes, true);
if (num_nodes <= 0) {
return 1;
}
for (int i = 0; i < num_nodes; ++i) {
const Family family = nodes[i].ip_port.ip.family;
if (net_family_is_ipv4(family) || net_family_is_ipv6(family)) {
dht_getnodes(onion_c->dht, &nodes[i].ip_port, nodes[i].public_key, onion_c->friends_list[friend_num].dht_public_key);
} else if (net_family_is_tcp_ipv4(family) || net_family_is_tcp_ipv6(family)) {
if (onion_c->friends_list[friend_num].tcp_relay_node_callback != nullptr) {
void *obj = onion_c->friends_list[friend_num].tcp_relay_node_callback_object;
const uint32_t number = onion_c->friends_list[friend_num].tcp_relay_node_callback_number;
onion_c->friends_list[friend_num].tcp_relay_node_callback(obj, number, &nodes[i].ip_port, nodes[i].public_key);
}
}
}
}
return 0;
}
non_null()
static int handle_tcp_onion(void *object, const uint8_t *data, uint16_t length, void *userdata)
{
if (length == 0) {
return 1;
}
IP_Port ip_port = {{{0}}};
ip_port.ip.family = net_family_tcp_server();
if (data[0] == NET_PACKET_ANNOUNCE_RESPONSE) {
return handle_announce_response(object, &ip_port, data, length, userdata);
}
if (data[0] == NET_PACKET_ANNOUNCE_RESPONSE_OLD) {
return handle_announce_response_old(object, &ip_port, data, length, userdata);
}
if (data[0] == NET_PACKET_ONION_DATA_RESPONSE) {
return handle_data_response(object, &ip_port, data, length, userdata);
}
return 1;
}
/** @brief Send data of length length to friendnum.
* Maximum length of data is ONION_CLIENT_MAX_DATA_SIZE.
* This data will be received by the friend using the Onion_Data_Handlers callbacks.
*
* Even if this function succeeds, the friend might not receive any data.
*
* return the number of packets sent on success
* return -1 on failure.
*/
int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
if (length + DATA_IN_RESPONSE_MIN_SIZE > MAX_DATA_REQUEST_SIZE) {
return -1;
}
if (length == 0) {
return -1;
}
unsigned int good_nodes[MAX_ONION_CLIENTS];
unsigned int num_good = 0;
unsigned int num_nodes = 0;
const Onion_Node *node_list = onion_c->friends_list[friend_num].clients_list;
for (unsigned int i = 0; i < MAX_ONION_CLIENTS; ++i) {
if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) {
continue;
}
++num_nodes;
if (node_list[i].is_stored != 0) {
good_nodes[num_good] = i;
++num_good;
}
}
if (num_good < (num_nodes - 1) / 4 + 1) {
return -1;
}
uint8_t nonce[CRYPTO_NONCE_SIZE];
random_nonce(onion_c->rng, nonce);
const uint16_t packet_size = DATA_IN_RESPONSE_MIN_SIZE + length;
VLA(uint8_t, packet, packet_size);
memcpy(packet, nc_get_self_public_key(onion_c->c), CRYPTO_PUBLIC_KEY_SIZE);
int len = encrypt_data(onion_c->mem, onion_c->friends_list[friend_num].real_public_key,
nc_get_self_secret_key(onion_c->c), nonce, data,
length, packet + CRYPTO_PUBLIC_KEY_SIZE);
if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE != packet_size) {
return -1;
}
unsigned int good = 0;
for (unsigned int i = 0; i < num_good; ++i) {
Onion_Path path;
if (random_path(onion_c, &onion_c->onion_paths_friends, -1, &path) == -1) {
continue;
}
uint8_t o_packet[ONION_MAX_PACKET_SIZE];
len = create_data_request(
onion_c->mem, onion_c->rng, o_packet, sizeof(o_packet), onion_c->friends_list[friend_num].real_public_key,
node_list[good_nodes[i]].data_public_key, nonce, packet, packet_size);
if (len == -1) {
continue;
}
if (send_onion_packet_tcp_udp(onion_c, &path, &node_list[good_nodes[i]].ip_port, o_packet, len) == 0) {
++good;
}
}
return good;
}
/** @brief Try to send the dht public key via the DHT instead of onion
*
* Even if this function succeeds, the friend might not receive any data.
*
* return the number of packets sent on success
* return -1 on failure.
*/
non_null()
static int send_dht_dhtpk(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
if (!onion_c->friends_list[friend_num].know_dht_public_key) {
return -1;
}
uint8_t nonce[CRYPTO_NONCE_SIZE];
random_nonce(onion_c->rng, nonce);
const uint16_t temp_size = DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE + length;
VLA(uint8_t, temp, temp_size);
memcpy(temp, nc_get_self_public_key(onion_c->c), CRYPTO_PUBLIC_KEY_SIZE);
memcpy(temp + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE);
int len = encrypt_data(onion_c->mem, onion_c->friends_list[friend_num].real_public_key,
nc_get_self_secret_key(onion_c->c), nonce, data,
length, temp + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE);
if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE != temp_size) {
return -1;
}
uint8_t packet_data[MAX_CRYPTO_REQUEST_SIZE];
len = create_request(
onion_c->mem, onion_c->rng, dht_get_self_public_key(onion_c->dht), dht_get_self_secret_key(onion_c->dht), packet_data,
onion_c->friends_list[friend_num].dht_public_key, temp, temp_size, CRYPTO_PACKET_DHTPK);
assert(len <= UINT16_MAX);
const Packet packet = {packet_data, (uint16_t)len};
if (len == -1) {
return -1;
}
return route_to_friend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, &packet);
}
non_null()
static int handle_dht_dhtpk(void *object, const IP_Port *source, const uint8_t *source_pubkey, const uint8_t *packet,
uint16_t length, void *userdata)
{
Onion_Client *onion_c = (Onion_Client *)object;
if (length < DHTPK_DATA_MIN_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) {
return 1;
}
if (length > DHTPK_DATA_MAX_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) {
return 1;
}
uint8_t plain[DHTPK_DATA_MAX_LENGTH];
const int len = decrypt_data(onion_c->mem, packet, nc_get_self_secret_key(onion_c->c),
packet + CRYPTO_PUBLIC_KEY_SIZE,
packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE,
length - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), plain);
if (len != length - (DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE)) {
return 1;
}
if (!pk_equal(source_pubkey, plain + 1 + sizeof(uint64_t))) {
return 1;
}
return handle_dhtpk_announce(onion_c, packet, plain, len, userdata);
}
/** @brief Send the packets to tell our friends what our DHT public key is.
*
* if onion_dht_both is 0, use only the onion to send the packet.
* if it is 1, use only the dht.
* if it is something else, use both.
*
* return the number of packets sent on success
* return -1 on failure.
*/
non_null()
static int send_dhtpk_announce(Onion_Client *onion_c, uint16_t friend_num, uint8_t onion_dht_both)
{
if (friend_num >= onion_c->num_friends) {
return -1;
}
uint8_t data[DHTPK_DATA_MAX_LENGTH];
data[0] = ONION_DATA_DHTPK;
const uint64_t no_replay = mono_time_get(onion_c->mono_time);
net_pack_u64(data + 1, no_replay);
memcpy(data + 1 + sizeof(uint64_t), dht_get_self_public_key(onion_c->dht), CRYPTO_PUBLIC_KEY_SIZE);
Node_format nodes[MAX_SENT_NODES];
const uint16_t num_relays = copy_connected_tcp_relays(onion_c->c, nodes, MAX_SENT_NODES / 2);
uint16_t num_nodes = closelist_nodes(onion_c->dht, &nodes[num_relays], MAX_SENT_NODES - num_relays);
num_nodes += num_relays;
int nodes_len = 0;
if (num_nodes != 0) {
nodes_len = pack_nodes(onion_c->logger, data + DHTPK_DATA_MIN_LENGTH, DHTPK_DATA_MAX_LENGTH - DHTPK_DATA_MIN_LENGTH,
nodes, num_nodes);
if (nodes_len <= 0) {
return -1;
}
}
int num1 = -1;
int num2 = -1;
if (onion_dht_both != 1) {
num1 = send_onion_data(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len);
}
if (onion_dht_both != 0) {
num2 = send_dht_dhtpk(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len);
}
if (num1 == -1) {
return num2;
}
if (num2 == -1) {
return num1;
}
return num1 + num2;
}
/** @brief Get the friend_num of a friend.
*
* return -1 on failure.
* return friend number on success.
*/
int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key)
{
for (unsigned int i = 0; i < onion_c->num_friends; ++i) {
if (!onion_c->friends_list[i].is_valid) {
continue;
}
if (pk_equal(public_key, onion_c->friends_list[i].real_public_key)) {
return i;
}
}
return -1;
}
/** @brief Set the size of the friend list to num.
*
* @retval -1 if mem_vrealloc fails.
* @retval 0 if it succeeds.
*/
non_null()
static int realloc_onion_friends(Onion_Client *onion_c, uint32_t num)
{
if (num == 0) {
mem_delete(onion_c->mem, onion_c->friends_list);
onion_c->friends_list = nullptr;
return 0;
}
Onion_Friend *newonion_friends = (Onion_Friend *)mem_vrealloc(onion_c->mem, onion_c->friends_list, num, sizeof(Onion_Friend));
if (newonion_friends == nullptr) {
return -1;
}
onion_c->friends_list = newonion_friends;
return 0;
}
/** @brief Add a friend who we want to connect to.
*
* return -1 on failure.
* return the friend number on success or if the friend was already added.
*/
int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key)
{
const int num = onion_friend_num(onion_c, public_key);
if (num != -1) {
return num;
}
unsigned int index = -1;
for (unsigned int i = 0; i < onion_c->num_friends; ++i) {
if (!onion_c->friends_list[i].is_valid) {
index = i;
break;
}
}
if (index == (uint32_t) -1) {
if (realloc_onion_friends(onion_c, onion_c->num_friends + 1) == -1) {
return -1;
}
index = onion_c->num_friends;
onion_c->friends_list[onion_c->num_friends] = empty_onion_friend;
++onion_c->num_friends;
}
onion_c->friends_list[index].is_valid = true;
memcpy(onion_c->friends_list[index].real_public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE);
crypto_new_keypair(onion_c->rng, onion_c->friends_list[index].temp_public_key,
onion_c->friends_list[index].temp_secret_key);
return index;
}
/** @brief Delete a friend.
*
* return -1 on failure.
* return the deleted friend number on success.
*/
int onion_delfriend(Onion_Client *onion_c, int friend_num)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
#if 0
if (onion_c->friends_list[friend_num].know_dht_public_key) {
dht_delfriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, 0);
}
#endif /* 0 */
crypto_memzero(&onion_c->friends_list[friend_num], sizeof(Onion_Friend));
unsigned int i;
for (i = onion_c->num_friends; i != 0; --i) {
if (onion_c->friends_list[i - 1].is_valid) {
break;
}
}
if (onion_c->num_friends != i) {
onion_c->num_friends = i;
realloc_onion_friends(onion_c, onion_c->num_friends);
}
return friend_num;
}
/** @brief Set the function for this friend that will be callbacked with object and number
* when that friend gives us one of the TCP relays they are connected to.
*
* object and number will be passed as argument to this function.
*
* return -1 on failure.
* return 0 on success.
*/
int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num,
recv_tcp_relay_cb *callback, void *object, uint32_t number)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
onion_c->friends_list[friend_num].tcp_relay_node_callback = callback;
onion_c->friends_list[friend_num].tcp_relay_node_callback_object = object;
onion_c->friends_list[friend_num].tcp_relay_node_callback_number = number;
return 0;
}
/** @brief Set the function for this friend that will be callbacked with object and number
* when that friend gives us their DHT temporary public key.
*
* object and number will be passed as argument to this function.
*
* return -1 on failure.
* return 0 on success.
*/
int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num,
onion_dht_pk_cb *function, void *object, uint32_t number)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
onion_c->friends_list[friend_num].dht_pk_callback = function;
onion_c->friends_list[friend_num].dht_pk_callback_object = object;
onion_c->friends_list[friend_num].dht_pk_callback_number = number;
return 0;
}
/** @brief Set a friend's DHT public key.
*
* return -1 on failure.
* return 0 on success.
*/
int onion_set_friend_dht_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
if (!onion_c->friends_list[friend_num].is_valid) {
return -1;
}
if (onion_c->friends_list[friend_num].know_dht_public_key) {
if (pk_equal(dht_key, onion_c->friends_list[friend_num].dht_public_key)) {
return -1;
}
}
onion_c->friends_list[friend_num].know_dht_public_key = true;
memcpy(onion_c->friends_list[friend_num].dht_public_key, dht_key, CRYPTO_PUBLIC_KEY_SIZE);
return 0;
}
/** @brief Copy friends DHT public key into dht_key.
*
* return 0 on failure (no key copied).
* return 1 on success (key copied).
*/
unsigned int onion_getfriend_dht_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return 0;
}
if (!onion_c->friends_list[friend_num].is_valid) {
return 0;
}
if (!onion_c->friends_list[friend_num].know_dht_public_key) {
return 0;
}
memcpy(dht_key, onion_c->friends_list[friend_num].dht_public_key, CRYPTO_PUBLIC_KEY_SIZE);
return 1;
}
/** @brief Get the ip of friend friendnum and put it in ip_port
*
* @retval -1 if public_key does NOT refer to a friend
* @retval 0 if public_key refers to a friend and we failed to find the friend (yet)
* @retval 1 if public_key refers to a friend and we found them
*/
int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port)
{
uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE];
if (onion_getfriend_dht_pubkey(onion_c, friend_num, dht_public_key) == 0) {
return -1;
}
return dht_getfriendip(onion_c->dht, dht_public_key, ip_port);
}
/** @brief Set if friend is online or not.
*
* NOTE: This function is there and should be used so that we don't send
* useless packets to the friend if they are online.
*
* return -1 on failure.
* return 0 on success.
*/
int onion_set_friend_online(Onion_Client *onion_c, int friend_num, bool is_online)
{
if ((uint32_t)friend_num >= onion_c->num_friends) {
return -1;
}
onion_c->friends_list[friend_num].is_online = is_online;
/* This should prevent some clock related issues */
if (!is_online) {
onion_c->friends_list[friend_num].last_noreplay = 0;
onion_c->friends_list[friend_num].run_count = 0;
}
return 0;
}
non_null()
static void populate_path_nodes(Onion_Client *onion_c)
{
Node_format node_list[MAX_FRIEND_CLIENTS];
const unsigned int num_nodes = randfriends_nodes(onion_c->dht, node_list, MAX_FRIEND_CLIENTS);
for (unsigned int i = 0; i < num_nodes; ++i) {
onion_add_path_node(onion_c, &node_list[i].ip_port, node_list[i].public_key);
}
}
/* How often we ping new friends per node */
#define ANNOUNCE_FRIEND_NEW_INTERVAL 3
/* How long we consider a friend new based on the value of their run_count */
#define ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING 5
/* How often we try to re-populate the nodes lists if we don't meet a minimum threshhold of nodes */
#define ANNOUNCE_POPULATE_TIMEOUT (60 * 10)
/* The max time between lookup requests for a friend per node */
#define ANNOUNCE_FRIEND_MAX_INTERVAL (60 * 60)
/* Max exponent when calculating the announce request interval */
#define MAX_RUN_COUNT_EXPONENT 12
non_null()
static void do_friend(Onion_Client *onion_c, uint16_t friendnum)
{
if (friendnum >= onion_c->num_friends) {
return;
}
Onion_Friend *o_friend = &onion_c->friends_list[friendnum];
if (!o_friend->is_valid) {
return;
}
uint32_t interval;
const uint64_t tm = mono_time_get(onion_c->mono_time);
const bool friend_is_new = o_friend->run_count <= ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING;
if (!friend_is_new) {
// how often we ping a node for a friend depends on how many times we've already tried.
// the interval increases exponentially, as the longer a friend has been offline, the less
// likely the case is that they're online and failed to find us
const uint32_t c = 1 << min_u32(MAX_RUN_COUNT_EXPONENT, o_friend->run_count - 2);
interval = min_u32(c, ANNOUNCE_FRIEND_MAX_INTERVAL);
} else {
interval = ANNOUNCE_FRIEND_NEW_INTERVAL;
}
if (o_friend->is_online) {
return;
}
assert(interval >= ANNOUNCE_FRIEND_NEW_INTERVAL); // an int overflow would be devastating
/* send packets to friend telling them our DHT public key. */
if (mono_time_is_timeout(onion_c->mono_time, onion_c->friends_list[friendnum].last_dht_pk_onion_sent,
ONION_DHTPK_SEND_INTERVAL)) {
if (send_dhtpk_announce(onion_c, friendnum, 0) >= 1) {
onion_c->friends_list[friendnum].last_dht_pk_onion_sent = tm;
}
}
if (mono_time_is_timeout(onion_c->mono_time, onion_c->friends_list[friendnum].last_dht_pk_dht_sent,
DHT_DHTPK_SEND_INTERVAL)) {
if (send_dhtpk_announce(onion_c, friendnum, 1) >= 1) {
onion_c->friends_list[friendnum].last_dht_pk_dht_sent = tm;
}
}
uint16_t count = 0; // number of alive path nodes
Onion_Node *node_list = o_friend->clients_list;
for (unsigned i = 0; i < MAX_ONION_CLIENTS; ++i) {
if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) {
continue;
}
++count;
// we don't want new nodes to be pinged immediately
if (node_list[i].last_pinged == 0) {
node_list[i].last_pinged = tm;
continue;
}
// node hasn't responded in a while so we skip it
if (node_list[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) {
continue;
}
// space requests out between nodes
if (!mono_time_is_timeout(onion_c->mono_time, o_friend->time_last_pinged, interval / (MAX_ONION_CLIENTS / 2))) {
continue;
}
if (!mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, interval)) {
continue;
}
if (client_send_announce_request(onion_c, friendnum + 1, &node_list[i].ip_port,
node_list[i].public_key, nullptr, -1) == 0) {
node_list[i].last_pinged = tm;
o_friend->time_last_pinged = tm;
++node_list[i].pings_since_last_response;
++o_friend->pings;
if (o_friend->pings % (MAX_ONION_CLIENTS / 2) == 0) {
++o_friend->run_count;
}
}
}
if (count == MAX_ONION_CLIENTS) {
if (!friend_is_new) {
o_friend->last_populated = tm;
}
return;
}
// check if path nodes list for this friend needs to be repopulated
if (count <= MAX_ONION_CLIENTS / 2
|| mono_time_is_timeout(onion_c->mono_time, o_friend->last_populated, ANNOUNCE_POPULATE_TIMEOUT)) {
const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES);
const uint16_t n = min_u16(num_nodes, MAX_PATH_NODES / 4);
if (n == 0) {
return;
}
o_friend->last_populated = tm;
for (uint16_t i = 0; i < n; ++i) {
const uint32_t num = random_range_u32(onion_c->rng, num_nodes);
client_send_announce_request(onion_c, friendnum + 1, &onion_c->path_nodes[num].ip_port,
onion_c->path_nodes[num].public_key, nullptr, -1);
}
}
}
/** Function to call when onion data packet with contents beginning with byte is received. */
void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_cb *cb, void *object)
{
onion_c->onion_data_handlers[byte].function = cb;
onion_c->onion_data_handlers[byte].object = object;
}
void onion_group_announce_register(Onion_Client *onion_c, onion_group_announce_cb *func, void *user_data)
{
onion_c->group_announce_response = func;
onion_c->group_announce_response_user_data = user_data;
}
#define ANNOUNCE_INTERVAL_NOT_ANNOUNCED 3
#define ANNOUNCE_INTERVAL_ANNOUNCED ONION_NODE_PING_INTERVAL
#define TIME_TO_STABLE (ONION_NODE_PING_INTERVAL * 6)
#define ANNOUNCE_INTERVAL_STABLE (ONION_NODE_PING_INTERVAL * 8)
non_null()
static bool key_list_contains(const uint8_t *const *keys, uint16_t keys_size, const uint8_t *public_key)
{
for (uint16_t i = 0; i < keys_size; ++i) {
if (memeq(keys[i], CRYPTO_PUBLIC_KEY_SIZE, public_key, CRYPTO_PUBLIC_KEY_SIZE)) {
return true;
}
}
return false;
}
/** Does path with path_num exist. */
non_null()
static bool path_exists(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, uint32_t path_num)
{
if (path_timed_out(mono_time, onion_paths, path_num)) {
return false;
}
return onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num;
}
/**
* A node/path is considered "stable" if it has survived for at least TIME_TO_STABLE
* and the latest packets sent to it are not timing out.
*/
non_null()
static bool path_is_stable(const Mono_Time *mono_time, const Onion_Client_Paths *paths,
uint32_t pathnum, const Onion_Node *node)
{
return mono_time_is_timeout(mono_time, node->added_time, TIME_TO_STABLE)
&& !(node->pings_since_last_response > 0
&& mono_time_is_timeout(mono_time, node->last_pinged, ONION_NODE_TIMEOUT))
&& mono_time_is_timeout(mono_time, paths->path_creation_time[pathnum], TIME_TO_STABLE)
&& !(paths->last_path_used_times[pathnum] > 0
&& mono_time_is_timeout(mono_time, paths->last_path_used[pathnum], ONION_PATH_TIMEOUT));
}
non_null()
static void do_announce(Onion_Client *onion_c)
{
unsigned int count = 0;
Onion_Node *node_list = onion_c->clients_announce_list;
for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) {
if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) {
continue;
}
++count;
/* Don't announce ourselves the first time this is run to new peers */
if (node_list[i].last_pinged == 0) {
node_list[i].last_pinged = 1;
continue;
}
if (node_list[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) {
continue;
}
unsigned int interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED;
if (node_list[i].is_stored != 0
&& path_exists(onion_c->mono_time, &onion_c->onion_paths_self, node_list[i].path_used)) {
interval = ANNOUNCE_INTERVAL_ANNOUNCED;
const uint32_t pathnum = node_list[i].path_used % NUMBER_ONION_PATHS;
/* If a node/path is considered "stable", it can be pinged less aggressively. */
if (path_is_stable(onion_c->mono_time, &onion_c->onion_paths_self, pathnum, &node_list[i])) {
interval = ANNOUNCE_INTERVAL_STABLE;
}
}
if (mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, interval)
|| mono_time_is_timeout(onion_c->mono_time, onion_c->last_announce, ONION_NODE_PING_INTERVAL)) {
uint32_t path_to_use = node_list[i].path_used;
if (node_list[i].pings_since_last_response == ONION_NODE_MAX_PINGS - 1
&& mono_time_is_timeout(onion_c->mono_time, node_list[i].added_time, TIME_TO_STABLE)) {
/* Last chance for a long-lived node - try a random path */
path_to_use = -1;
}
if (client_send_announce_request(onion_c, 0, &node_list[i].ip_port, node_list[i].public_key,
node_list[i].ping_id, path_to_use) == 0) {
node_list[i].last_pinged = mono_time_get(onion_c->mono_time);
++node_list[i].pings_since_last_response;
onion_c->last_announce = mono_time_get(onion_c->mono_time);
}
}
}
if (count == MAX_ONION_CLIENTS_ANNOUNCE) {
onion_c->last_populated = mono_time_get(onion_c->mono_time);
return;
}
// check if list needs to be re-populated
if (count <= MAX_ONION_CLIENTS_ANNOUNCE / 2
|| mono_time_is_timeout(onion_c->mono_time, onion_c->last_populated, ANNOUNCE_POPULATE_TIMEOUT)) {
uint16_t num_nodes;
const Node_format *path_nodes;
if (onion_c->path_nodes_index == 0) {
num_nodes = min_u16(onion_c->path_nodes_index_bs, MAX_PATH_NODES);
path_nodes = onion_c->path_nodes_bs;
} else {
num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES);
path_nodes = onion_c->path_nodes;
}
if (num_nodes == 0) {
return;
}
// Don't send announces to the same node twice. If we don't have many nodes,
// the random selection below may have overlaps. This ensures that we deduplicate
// nodes before sending packets to save some bandwidth.
//
// TODO(iphydf): Figure out why on esp32, this is necessary for the onion
// connection to succeed. This is an optimisation and shouldn't be necessary.
const uint8_t *targets[MAX_ONION_CLIENTS_ANNOUNCE / 2];
unsigned int targets_count = 0;
for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE / 2; ++i) {
const uint32_t num = random_range_u32(onion_c->rng, num_nodes);
const Node_format *target = &path_nodes[num];
if (!key_list_contains(targets, targets_count, target->public_key)) {
client_send_announce_request(onion_c, 0, &target->ip_port, target->public_key, nullptr, -1);
targets[targets_count] = target->public_key;
++targets_count;
assert(targets_count <= MAX_ONION_CLIENTS_ANNOUNCE / 2);
} else {
Ip_Ntoa ip_str;
LOGGER_TRACE(onion_c->logger, "not sending repeated announce request to %s:%d",
net_ip_ntoa(&target->ip_port.ip, &ip_str), net_ntohs(target->ip_port.port));
}
}
}
}
/**
* @retval false if we are not connected to the network.
* @retval true if we are.
*/
non_null()
static bool onion_isconnected(Onion_Client *onion_c)
{
unsigned int live = 0;
unsigned int announced = 0;
if (mono_time_is_timeout(onion_c->mono_time, onion_c->last_packet_recv, ONION_OFFLINE_TIMEOUT)) {
LOGGER_TRACE(onion_c->logger, "onion is NOT connected: last packet received at %llu (timeout=%u)",
(unsigned long long)onion_c->last_packet_recv, ONION_OFFLINE_TIMEOUT);
onion_c->last_populated = 0;
return false;
}
if (onion_c->path_nodes_index == 0) {
LOGGER_TRACE(onion_c->logger, "onion is NOT connected: no path nodes available");
onion_c->last_populated = 0;
return false;
}
for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) {
if (!onion_node_timed_out(&onion_c->clients_announce_list[i], onion_c->mono_time)) {
++live;
if (onion_c->clients_announce_list[i].is_stored != 0) {
++announced;
}
}
}
unsigned int pnodes = onion_c->path_nodes_index;
if (pnodes > MAX_ONION_CLIENTS_ANNOUNCE) {
pnodes = MAX_ONION_CLIENTS_ANNOUNCE;
}
/* Consider ourselves online if we are announced to half or more nodes
* we are connected to */
if (live != 0 && announced != 0) {
if ((live / 2) <= announced && (pnodes / 2) <= live) {
LOGGER_TRACE(onion_c->logger, "onion is connected: %u live nodes, %u announced, %d path nodes",
live, announced, pnodes);
return true;
}
}
onion_c->last_populated = 0;
LOGGER_TRACE(onion_c->logger, "onion is NOT connected: %u live nodes, %u announced, %d path nodes",
live, announced, pnodes);
return false;
}
non_null()
static void reset_friend_run_counts(Onion_Client *onion_c)
{
for (uint16_t i = 0; i < onion_c->num_friends; ++i) {
Onion_Friend *o_friend = &onion_c->friends_list[i];
if (o_friend->is_valid) {
o_friend->run_count = 0;
}
}
}
#define ONION_CONNECTION_SECONDS 3
#define ONION_CONNECTED_TIMEOUT 10
Onion_Connection_Status onion_connection_status(const Onion_Client *onion_c)
{
if (onion_c->onion_connected >= ONION_CONNECTION_SECONDS) {
if (onion_c->udp_connected) {
return ONION_CONNECTION_STATUS_UDP;
}
return ONION_CONNECTION_STATUS_TCP;
}
return ONION_CONNECTION_STATUS_NONE;
}
void do_onion_client(Onion_Client *onion_c)
{
if (onion_c->last_run == mono_time_get(onion_c->mono_time)) {
return;
}
if (mono_time_is_timeout(onion_c->mono_time, onion_c->first_run, ONION_CONNECTION_SECONDS)) {
populate_path_nodes(onion_c);
do_announce(onion_c);
}
if (onion_isconnected(onion_c)) {
if (mono_time_is_timeout(onion_c->mono_time, onion_c->last_time_connected, ONION_CONNECTED_TIMEOUT)) {
reset_friend_run_counts(onion_c);
}
onion_c->last_time_connected = mono_time_get(onion_c->mono_time);
if (onion_c->onion_connected < ONION_CONNECTION_SECONDS * 2) {
++onion_c->onion_connected;
}
} else {
if (onion_c->onion_connected != 0) {
--onion_c->onion_connected;
}
}
onion_c->udp_connected = dht_non_lan_connected(onion_c->dht);
if (mono_time_is_timeout(onion_c->mono_time, onion_c->first_run, ONION_CONNECTION_SECONDS * 2)) {
set_tcp_onion_status(nc_get_tcp_c(onion_c->c), !onion_c->udp_connected);
}
if (onion_connection_status(onion_c) != ONION_CONNECTION_STATUS_NONE) {
for (unsigned i = 0; i < onion_c->num_friends; ++i) {
do_friend(onion_c, i);
}
}
if (onion_c->last_run == 0) {
onion_c->first_run = mono_time_get(onion_c->mono_time);
}
onion_c->last_run = mono_time_get(onion_c->mono_time);
}
Onion_Client *new_onion_client(const Logger *logger, const Memory *mem, const Random *rng, const Mono_Time *mono_time, Net_Crypto *c)
{
if (c == nullptr) {
return nullptr;
}
Onion_Client *onion_c = (Onion_Client *)mem_alloc(mem, sizeof(Onion_Client));
if (onion_c == nullptr) {
return nullptr;
}
onion_c->announce_ping_array = ping_array_new(mem, ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT);
if (onion_c->announce_ping_array == nullptr) {
mem_delete(mem, onion_c);
return nullptr;
}
onion_c->mono_time = mono_time;
onion_c->logger = logger;
onion_c->rng = rng;
onion_c->mem = mem;
onion_c->dht = nc_get_dht(c);
onion_c->net = dht_get_net(onion_c->dht);
onion_c->c = c;
new_symmetric_key(rng, onion_c->secret_symmetric_key);
crypto_new_keypair(rng, onion_c->temp_public_key, onion_c->temp_secret_key);
networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_announce_response, onion_c);
networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE_OLD, &handle_announce_response_old, onion_c);
networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_data_response, onion_c);
oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, &handle_dhtpk_announce, onion_c);
cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, &handle_dht_dhtpk, onion_c);
set_onion_packet_tcp_connection_callback(nc_get_tcp_c(onion_c->c), &handle_tcp_onion, onion_c);
return onion_c;
}
void kill_onion_client(Onion_Client *onion_c)
{
if (onion_c == nullptr) {
return;
}
const Memory *mem = onion_c->mem;
ping_array_kill(onion_c->announce_ping_array);
realloc_onion_friends(onion_c, 0);
networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, nullptr, nullptr);
networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE_OLD, nullptr, nullptr);
networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, nullptr, nullptr);
oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, nullptr, nullptr);
cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, nullptr, nullptr);
set_onion_packet_tcp_connection_callback(nc_get_tcp_c(onion_c->c), nullptr, nullptr);
crypto_memzero(onion_c, sizeof(Onion_Client));
mem_delete(mem, onion_c);
}