forked from Green-Sky/tomato
Green Sky
aae086cc65
b03b571272 fix: flaky tcp test This only fixes the symptoms, not the real problem. Sometimes or consistently on some platforms a socket might need a moment before it can be written to. 32e67ab4c2 cleanup: use typedef for private message ID's in callback 7b1db6adc1 feat: add message IDs to private group messages 99e0bcc27d refactor: Observers/ignored peers can now send and receive custom packets b3c3c49d26 fix: Disable IPv6 in Windows cross-compilation tests e742deddff feat: Check hashes of Windows dependencies when cross-compiling dfb9a0b02b fix: Test the current Windows Dockerfile, not an old Dockerhub image 14de93ccec chore: Use WineHQ's Wine as Debian Bookworm's crashes ed37616249 docs: Update the Windows cross-compilation section 9bb79c174f cleanup: Remove a couple of unnecessary misc_tools dependencies 19475adb70 chore: Statically link OpenMP into the cracker fun util on Windows 1be311e51f feat: Build the fun utils when cross-compiling to Windows 88133f8446 chore: Strip Windows binaries 3cc0ae7535 refactor: Copy over all of the required static dependencies c4fa8f7fb1 feat: Generate .def, .exp and .lib files when building for Windows 74bbac5363 feat: Let CMake create the dll instead of doing so ourselves 246642e9ae feat: Harden Windows cross-compilation 8d431c0d11 chore: Bump Windows build dependency versions e519f7998b fix: Remove unnecessary wsock32 dependency on Windows ed2b60c217 chore: Use a specific non-broken slimcc version. d7f21010a1 chore: Update github actions. e71a68b7f2 docs: Update the list of CMake options 77e08876ff chore: Remove mod and founder from group API naming scheme 12bc042767 docs: add the experimental api build option to INSTALL.md e1fa5cae96 refactor: Rename Queries to Query to align with other enums. be82a3ea30 fix: Correct type for conference offline peer numbers. 0627c36716 test: Add pkgsrc build. 92578afe4b test: Add FreeBSD VM action on GitHub. 52ece0f57b test: Build toxcore on NetBSD (VM). 3fe8ee2c11 chore: Only install tox_private.h on request. 9a8dfa06ab fix: save_compatibility_test failing on big-endian systems 86f5e55578 fix: Don't serve files from websockify. 710eb674a5 fix: Correctly pass extended public keys to group moderation code. 021db7031c refactor: Use `struct`s for extended public/secret keys. a1e999fd80 chore: Compile libsodium reference implementation with compcert. fbe3c19cf5 cleanup: correct a few nullable annotations 623e3ee5c3 cleanup: Don't use `memcpy` to cast arbitrary `struct`s to `uint8_t[]`. c71567dc18 fix: Pass array, not array pointer, to `memcmp`. 9b46a08144 cleanup: Never pass `void*` directly to `memcpy`. 5d7b7a7bbc refactor: Use tox rng to seed the keypair generation. 961891d568 cleanup: Small improvements found by PVS Studio. 8201019f0d chore: Disable NGC saving by default, enable through Tox_Options. 5dd9ee3f65 cleanup: Replace pointer arithmetic with explicit `&arr[i]`. ca4606d49d refactor: Use strong typedef for NGC peer id. 442213b722 cleanup: Simplify custom packet length check in NGC. 08d3393def fix: Correct a few potential null derefs in bootstrap daemon. b9877b32b0 fix: Add missing memunlock of local variable when it goes out of scope. dab5fe44b9 fix: Zero out stack-allocated secret key before return. f058103299 refactor: Make prune_gc_sanctions_list more obviously correct. 3ba7a0dec9 docs: Add static analysis tool list to README. 8d0811a0f3 docs: Run prettier-markdown on markdown files. 969e3a2bfc refactor: Fix network test not using the strong typedef 93c83fbc7c refactor: Use strong typedef instead of struct for `Socket`. 9fe18b176f fix: Fix some false positive from PVS Studio. 7c44379ccb cleanup: Check that WINXP macro exists before comparing it. 5c93231bef refactor: Make tox mutex non-recursive. aacff73939 docs: Fix up doxyfile. d55fc85ff5 docs: Add more documentation to crypto_core. 5bdaaaedb6 refactor: Remove `Tox *` from `tox_dispatch`. e202341e76 refactor: Don't rely on tox_dispatch passing tox in tests. 34df938f52 chore: Use C++ mode for clang-tidy. 8b05296a78 chore: Check that both gtest and gmock exist for tests. 42010660e1 test: Add slimcc compiler compatibility test. b473630321 chore: Add some comments to the astyle config. b7404f24f6 cleanup: Remove implicit bool conversions. 4e2dba4d9f chore: Reformat sources with astyle. 4359e3a6bc chore: Rename C++ headers to .hh suffixes. 0c05566e58 cleanup: Further `#include` cleanups. 8d29935b7a chore: Only check the bootstrap daemon checksum on release. f70e588bc6 cleanup: Add more `const` where possible. 511bfe39c8 cleanup: Use Bazel modules to enforce proper `#include` hygiene. 1710a0d091 refactor: Move pack/unpack `IP_Port` from DHT into network module. a975943564 chore: Really fix coverage docker image build. c08409390f chore: Fix post-submit coverage image. 39aadf8922 fix: Don't use `memcmp` to compare `IP_Port`s. d94246a906 fix: partially fix a bug that prevented group part messages from sending. eeaa039222 chore: Fix rpm build; add a CI check for it. 8328449c1a chore: Speed up docker builds a bit by reducing layer count. d6d67d56f3 cleanup: Add `const` where possible in auto tests. 6aa9e6850d cleanup: Minor cleanup of event unpack code. bdf460a3a9 refactor: Rename `system_{memory,...}` to `os_{memory,...}`. 203e1af81e fix: a few off by one errors in group autotests 5c093c4888 cleanup: Remove all uses of `SIZEOF_VLA`. 662c2140f3 test: Add goblint static analyser. 8f07755834 cleanup: Use `memzero(x, s)` instead of `memset(x, 0, s)`. a7258e40cf cleanup: Use explicit 0 instead of `PACKET_ID_PADDING`. 6370d0f15d cleanup: Expand the `Tox_Options` accessor macros. 14a1a0b9bd cleanup: Remove plan9 support. a05dccad13 test: Add a simple new/delete test for Tox. 1cdcf938b9 cleanup: Add comment after every `#endif`. ba99d4dc4b test: Fix comment I broke in the events test PR. e07248debb refactor: Migrate auto_tests to new events API. bdd42b5452 refactor: Add common msgpack array packer with callback. 3c659f5288 cleanup: Rename group to conference in groupav documentation. 89957be230 cleanup: Ensure handler params are named after callback params. c650d9d345 refactor: Pass `this` pointer as first param to s11n callbacks. e7fb91ddb8 refactor: Allow NULL pointers for byte arrays in events. 5e2c8cabc1 cleanup: make some improvements to group moderation test 259de4867e cleanup: Remove `bin_pack_{new,free}`. 21a8ff5895 cleanup: skip a do_gc iteration before removing peers marked for deletion 16809dc36e feat: Add dht_get_nodes_response event to the events system. git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: b03b5712720de9a9901ea12fd741f177327a7021
2230 lines
73 KiB
C
2230 lines
73 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 <stdlib.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 "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->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->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->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->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->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_Client_Cmp_Data {
|
|
const Mono_Time *mono_time;
|
|
const uint8_t *base_public_key;
|
|
Onion_Node entry;
|
|
} Onion_Client_Cmp_Data;
|
|
|
|
non_null()
|
|
static int onion_client_cmp_entry(const void *a, const void *b)
|
|
{
|
|
const Onion_Client_Cmp_Data *cmp1 = (const Onion_Client_Cmp_Data *)a;
|
|
const Onion_Client_Cmp_Data *cmp2 = (const Onion_Client_Cmp_Data *)b;
|
|
const Onion_Node entry1 = cmp1->entry;
|
|
const Onion_Node entry2 = cmp2->entry;
|
|
const uint8_t *cmp_public_key = cmp1->base_public_key;
|
|
|
|
const bool t1 = onion_node_timed_out(&entry1, cmp1->mono_time);
|
|
const bool t2 = onion_node_timed_out(&entry2, cmp2->mono_time);
|
|
|
|
if (t1 && t2) {
|
|
return 0;
|
|
}
|
|
|
|
if (t1) {
|
|
return -1;
|
|
}
|
|
|
|
if (t2) {
|
|
return 1;
|
|
}
|
|
|
|
const int closest = id_closest(cmp_public_key, entry1.public_key, entry2.public_key);
|
|
|
|
if (closest == 1) {
|
|
return 1;
|
|
}
|
|
|
|
if (closest == 2) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 qsort with each Client_data entry, so the
|
|
// comparison function can use it as the base of comparison.
|
|
Onion_Client_Cmp_Data *cmp_list = (Onion_Client_Cmp_Data *)mem_valloc(mem, length, sizeof(Onion_Client_Cmp_Data));
|
|
|
|
if (cmp_list == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
cmp_list[i].mono_time = mono_time;
|
|
cmp_list[i].base_public_key = comp_public_key;
|
|
cmp_list[i].entry = list[i];
|
|
}
|
|
|
|
qsort(cmp_list, length, sizeof(Onion_Client_Cmp_Data), onion_client_cmp_entry);
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
list[i] = cmp_list[i].entry;
|
|
}
|
|
|
|
mem_delete(mem, cmp_list);
|
|
}
|
|
|
|
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 int 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(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(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(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(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(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(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->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->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->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->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(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);
|
|
}
|