Skip to content

Commit

Permalink
Re-implement progressive backoff interval for friend finding
Browse files Browse the repository at this point in the history
We now exponentially reduce the frequency of friend lookups based on how many
lookups we've already made for an offline friend
  • Loading branch information
JFreegman committed Mar 3, 2022
1 parent af5dd43 commit 0dca481
Showing 1 changed file with 79 additions and 58 deletions.
137 changes: 79 additions & 58 deletions toxcore/onion_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ typedef struct Onion_Friend {

uint64_t last_seen;
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;
Expand Down Expand Up @@ -751,9 +754,9 @@ static int client_add_to_list(Onion_Client *onion_c, uint32_t num, const uint8_t
memcpy(node_list[index].ping_id, pingid_or_key, ONION_PING_ID_SIZE);
}

list_nodes[index].is_stored = is_stored;
list_nodes[index].timestamp = mono_time_get(onion_c->mono_time);
list_nodes[index].pings_since_last_response = 0;
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;
Expand Down Expand Up @@ -1000,7 +1003,7 @@ static int handle_dhtpk_announce(void *object, const uint8_t *source_pubkey, con
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);
len_nodes, true);

if (num_nodes <= 0) {
return 1;
Expand Down Expand Up @@ -1189,9 +1192,9 @@ static int handle_dht_dhtpk(void *object, const IP_Port *source, const uint8_t *

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);
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;
Expand Down Expand Up @@ -1520,39 +1523,42 @@ int onion_set_friend_online(Onion_Client *onion_c, int friend_num, bool is_onlin
non_null()
static void populate_path_nodes(Onion_Client *onion_c)
{
Node_format nodes_list[MAX_FRIEND_CLIENTS];
Node_format node_list[MAX_FRIEND_CLIENTS];

const unsigned int num_nodes = randfriends_nodes(onion_c->dht, nodes_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, &nodes_list[i].ip_port, nodes_list[i].public_key);
onion_add_path_node(onion_c, &node_list[i].ip_port, node_list[i].public_key);
}
}

non_null()
static void populate_path_nodes_tcp(Onion_Client *onion_c)
{
Node_format nodes_list[MAX_SENT_NODES];
Node_format node_list[MAX_SENT_NODES];

const unsigned int num_nodes = copy_connected_tcp_relays(onion_c->c, nodes_list, MAX_SENT_NODES);
const unsigned int num_nodes = copy_connected_tcp_relays(onion_c->c, node_list, MAX_SENT_NODES);

for (unsigned int i = 0; i < num_nodes; ++i) {
onion_add_bs_path_node(onion_c, &nodes_list[i].ip_port, nodes_list[i].public_key);
onion_add_bs_path_node(onion_c, &node_list[i].ip_port, node_list[i].public_key);
}
}

/* How often we send announce requests to a friend per node when they're new to the list */
#define ANNOUNCE_FRIEND_NEW_INTERVAL 10

/* How often we send announce requests to a friend per node when they've been in our list for a while */
#define ANNOUNCE_FRIEND_OLD_INTERVAL (60 * 3)
/* How often we ping new friends per node */
#define ANNOUNCE_FRIEND_NEW_INTERVAL 3

/* The number of successful runs of do_friends() before we consider a friend old */
#define ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING 10
/* 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)
{
Expand All @@ -1566,22 +1572,30 @@ static void do_friend(Onion_Client *onion_c, uint16_t friendnum)
return;
}

const bool friend_is_new = o_friend->run_count <= ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING;
uint32_t interval;
const uint64_t tm = mono_time_get(onion_c->mono_time);
uint64_t timeout = ANNOUNCE_FRIEND_OLD_INTERVAL;
const bool friend_is_new = o_friend->run_count <= ANNOUNCE_FRIEND_RUN_COUNT_BEGINNING;

if (!friend_is_new) {
if (o_friend->last_seen == 0) {
o_friend->last_seen = tm;
}

// 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 {
timeout = ANNOUNCE_FRIEND_NEW_INTERVAL;
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)) {
Expand All @@ -1598,41 +1612,47 @@ static void do_friend(Onion_Client *onion_c, uint16_t friendnum)
}

uint16_t count = 0; // number of alive path nodes
uint16_t pings = 0; // number of pings we successfully send

Onion_Node *list_nodes = o_friend->clients_list;
Onion_Node *node_list = o_friend->clients_list;

for (unsigned i = 0; i < MAX_ONION_CLIENTS; ++i) {
if (onion_node_timed_out(&list_nodes[i], onion_c->mono_time)) {
if (onion_node_timed_out(&node_list[i], onion_c->mono_time)) {
continue;
}

++count;

if (list_nodes[i].last_pinged == 0) {
list_nodes[i].last_pinged = tm;
// 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 (list_nodes[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) {
if (node_list[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) {
continue;
}

if (!mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, timeout)) {
// 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 (client_send_announce_request(onion_c, friendnum + 1, &list_nodes[i].ip_port,
list_nodes[i].public_key, nullptr, -1) == 0) {
list_nodes[i].last_pinged = tm;
++list_nodes[i].pings_since_last_response;
++pings;
if (!mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, interval)) {
continue;
}
}

if (pings > 0) {
++o_friend->run_count;
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) {
Expand All @@ -1644,7 +1664,7 @@ static void do_friend(Onion_Client *onion_c, uint16_t friendnum)
}

// check if path nodes list for this friend needs to be repopulated
if (count < MAX_ONION_CLIENTS / 2
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);
Expand Down Expand Up @@ -1681,61 +1701,62 @@ non_null()
static void do_announce(Onion_Client *onion_c)
{
unsigned int count = 0;
Onion_Node *list_nodes = onion_c->clients_announce_list;
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(&list_nodes[i], onion_c->mono_time)) {
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 (list_nodes[i].last_pinged == 0) {
list_nodes[i].last_pinged = 1;
if (node_list[i].last_pinged == 0) {
node_list[i].last_pinged = 1;
continue;
}

if (list_nodes[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) {
if (node_list[i].pings_since_last_response >= ONION_NODE_MAX_PINGS) {
continue;
}


unsigned int interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED;

if (list_nodes[i].is_stored != 0 && path_exists(onion_c->mono_time, &onion_c->onion_paths_self, list_nodes[i].path_used)) {
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 = list_nodes[i].path_used % NUMBER_ONION_PATHS;
const uint32_t pathnum = node_list[i].path_used % NUMBER_ONION_PATHS;

/* A node/path is considered "stable", and can be pinged less
* aggressively, if it has survived for at least TIME_TO_STABLE
* and the latest packets sent to it are not timing out.
*/
if (mono_time_is_timeout(onion_c->mono_time, list_nodes[i].added_time, TIME_TO_STABLE)
&& !(list_nodes[i].pings_since_last_response > 0
&& mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, ONION_NODE_TIMEOUT))
if (mono_time_is_timeout(onion_c->mono_time, node_list[i].added_time, TIME_TO_STABLE)
&& !(node_list[i].pings_since_last_response > 0
&& mono_time_is_timeout(onion_c->mono_time, node_list[i].last_pinged, ONION_NODE_TIMEOUT))
&& mono_time_is_timeout(onion_c->mono_time, onion_c->onion_paths_self.path_creation_time[pathnum], TIME_TO_STABLE)
&& !(onion_c->onion_paths_self.last_path_used_times[pathnum] > 0
&& mono_time_is_timeout(onion_c->mono_time, onion_c->onion_paths_self.last_path_used[pathnum], ONION_PATH_TIMEOUT))) {
interval = ANNOUNCE_INTERVAL_STABLE;
}
}

if (mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, interval)
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 = list_nodes[i].path_used;
uint32_t path_to_use = node_list[i].path_used;

if (list_nodes[i].pings_since_last_response == ONION_NODE_MAX_PINGS - 1
&& mono_time_is_timeout(onion_c->mono_time, list_nodes[i].added_time, TIME_TO_STABLE)) {
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, &list_nodes[i].ip_port, list_nodes[i].public_key,
list_nodes[i].ping_id, path_to_use) == 0) {
list_nodes[i].last_pinged = mono_time_get(onion_c->mono_time);
++list_nodes[i].pings_since_last_response;
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);
}
}
Expand All @@ -1747,7 +1768,7 @@ static void do_announce(Onion_Client *onion_c)
}

// check if list needs to be re-populated
if (count < MAX_ONION_CLIENTS_ANNOUNCE / 2
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;
Expand Down

0 comments on commit 0dca481

Please sign in to comment.