/* * ping.c -- Buffered pinging using cyclic arrays. * * This file is donated to the Tox Project. * Copyright 2013 plutooo * * Copyright (C) 2013 Tox project All Rights Reserved. * * This file is part of Tox. * * Tox is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tox is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tox. If not, see . * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "DHT.h" #include "ping.h" #include "network.h" #include "util.h" #define PING_NUM_MAX 512 /* Maximum newly announced nodes to ping per TIME_TO_PING seconds. */ #define MAX_TO_PING 8 /* Ping newly announced nodes to ping per TIME_TO_PING seconds*/ #define TIME_TO_PING 3 typedef struct { IP_Port ip_port; uint64_t id; uint64_t timestamp; uint8_t shared_key[crypto_box_BEFORENMBYTES]; } pinged_t; struct PING { DHT *dht; pinged_t pings[PING_NUM_MAX]; size_t num_pings; size_t pos_pings; Node_format to_ping[MAX_TO_PING]; uint64_t last_to_ping; }; static int is_ping_timeout(uint64_t time) { return is_timeout(time, PING_TIMEOUT); } static void remove_timeouts(PING *ping) // O(n) { size_t i, id; size_t new_pos = ping->pos_pings; size_t new_num = ping->num_pings; // Loop through buffer, oldest first. for (i = 0; i < ping->num_pings; i++) { id = (ping->pos_pings + i) % PING_NUM_MAX; if (is_ping_timeout(ping->pings[id].timestamp)) { new_pos++; new_num--; } // Break here because list is sorted. else { break; } } ping->num_pings = new_num; ping->pos_pings = new_pos % PING_NUM_MAX; } static uint64_t add_ping(PING *ping, IP_Port ipp, uint8_t *shared_encryption_key) // O(n) { size_t p; remove_timeouts(ping); /* Remove oldest ping if full buffer. */ if (ping->num_pings == PING_NUM_MAX) { ping->num_pings--; ping->pos_pings = (ping->pos_pings + 1) % PING_NUM_MAX; } /* Insert new ping at end of list. */ p = (ping->pos_pings + ping->num_pings) % PING_NUM_MAX; ping->pings[p].ip_port = ipp; ping->pings[p].timestamp = unix_time(); ping->pings[p].id = random_64b(); memcpy(ping->pings[p].shared_key, shared_encryption_key, crypto_box_BEFORENMBYTES); ping->num_pings++; return ping->pings[p].id; } /* checks if ip/port or ping_id are already in the list to ping * if both are set, both must match, otherwise the set must match * * returns 0 if neither is set or no match was found * returns the (index + 1) of the match if one was found */ static int is_pinging(PING *ping, IP_Port ipp, uint64_t ping_id) { // O(n) TODO: Replace this with something else. /* at least one MUST be set */ uint8_t ip_valid = ip_isset(&ipp.ip); if (!ip_valid && !ping_id) return 0; size_t i; remove_timeouts(ping); for (i = 0; i < ping->num_pings; i++) { size_t id = (ping->pos_pings + i) % PING_NUM_MAX; if (!ping_id || (ping->pings[id].id == ping_id)) if (!ip_valid || ipport_equal(&ping->pings[id].ip_port, &ipp)) return id + 1; } return 0; } #define DHT_PING_SIZE (1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES + sizeof(uint64_t) + crypto_box_MACBYTES) int send_ping_request(PING *ping, IP_Port ipp, uint8_t *client_id) { uint8_t pk[DHT_PING_SIZE]; int rc; uint64_t ping_id; if (is_pinging(ping, ipp, 0) || id_equal(client_id, ping->dht->self_public_key)) return 1; uint8_t shared_key[crypto_box_BEFORENMBYTES]; // generate key to encrypt ping_id with recipient privkey DHT_get_shared_key_sent(ping->dht, shared_key, client_id); // Generate random ping_id. ping_id = add_ping(ping, ipp, shared_key); pk[0] = NET_PACKET_PING_REQUEST; id_copy(pk + 1, ping->dht->self_public_key); // Our pubkey new_nonce(pk + 1 + CLIENT_ID_SIZE); // Generate new nonce rc = encrypt_data_symmetric(shared_key, pk + 1 + CLIENT_ID_SIZE, (uint8_t *) &ping_id, sizeof(ping_id), pk + 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES); if (rc != sizeof(ping_id) + crypto_box_MACBYTES) return 1; return sendpacket(ping->dht->net, ipp, pk, sizeof(pk)); } static int send_ping_response(PING *ping, IP_Port ipp, uint8_t *client_id, uint64_t ping_id, uint8_t *shared_encryption_key) { uint8_t pk[DHT_PING_SIZE]; int rc; if (id_equal(client_id, ping->dht->self_public_key)) return 1; pk[0] = NET_PACKET_PING_RESPONSE; id_copy(pk + 1, ping->dht->self_public_key); // Our pubkey new_nonce(pk + 1 + CLIENT_ID_SIZE); // Generate new nonce // Encrypt ping_id using recipient privkey rc = encrypt_data_symmetric(shared_encryption_key, pk + 1 + CLIENT_ID_SIZE, (uint8_t *) &ping_id, sizeof(ping_id), pk + 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES ); if (rc != sizeof(ping_id) + crypto_box_MACBYTES) return 1; return sendpacket(ping->dht->net, ipp, pk, sizeof(pk)); } static int handle_ping_request(void *_dht, IP_Port source, uint8_t *packet, uint32_t length) { DHT *dht = _dht; int rc; uint64_t ping_id; if (length != DHT_PING_SIZE) return 1; PING *ping = dht->ping; if (id_equal(packet + 1, ping->dht->self_public_key)) return 1; uint8_t shared_key[crypto_box_BEFORENMBYTES]; // Decrypt ping_id DHT_get_shared_key_recv(dht, shared_key, packet + 1); rc = decrypt_data_symmetric(shared_key, packet + 1 + CLIENT_ID_SIZE, packet + 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES, sizeof(ping_id) + crypto_box_MACBYTES, (uint8_t *) &ping_id ); if (rc != sizeof(ping_id)) return 1; // Send response send_ping_response(ping, source, packet + 1, ping_id, shared_key); add_to_ping(ping, packet + 1, source); return 0; } static int handle_ping_response(void *_dht, IP_Port source, uint8_t *packet, uint32_t length) { DHT *dht = _dht; int rc; uint64_t ping_id; if (length != DHT_PING_SIZE) return 1; PING *ping = dht->ping; if (id_equal(packet + 1, ping->dht->self_public_key)) return 1; int ping_index = is_pinging(ping, source, 0); if (!ping_index) return 1; --ping_index; // Decrypt ping_id rc = decrypt_data_symmetric(ping->pings[ping_index].shared_key, packet + 1 + CLIENT_ID_SIZE, packet + 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES, sizeof(ping_id) + crypto_box_MACBYTES, (uint8_t *) &ping_id); if (rc != sizeof(ping_id)) return 1; if (ping->pings[ping_index].id != ping_id) return 1; addto_lists(dht, source, packet + 1); return 0; } /* Add nodes to the to_ping list. * All nodes in this list are pinged every TIME_TO_PING seconds * and are then removed from the list. * If the list is full the nodes farthest from our client_id are replaced. * The purpose of this list is to enable quick integration of new nodes into the * network while preventing amplification attacks. * * return 0 if node was added. * return -1 if node was not added. */ int add_to_ping(PING *ping, uint8_t *client_id, IP_Port ip_port) { if (!ip_isset(&ip_port.ip)) return -1; uint32_t i; for (i = 0; i < MAX_TO_PING; ++i) { if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { memcpy(ping->to_ping[i].client_id, client_id, CLIENT_ID_SIZE); ipport_copy(&ping->to_ping[i].ip_port, &ip_port); return 0; } if (memcmp(ping->to_ping[i].client_id, client_id, CLIENT_ID_SIZE) == 0) { return -1; } } uint32_t r = rand(); for (i = 0; i < MAX_TO_PING; ++i) { if (id_closest(ping->dht->self_public_key, ping->to_ping[(i + r) % MAX_TO_PING].client_id, client_id) == 2) { memcpy(ping->to_ping[(i + r) % MAX_TO_PING].client_id, client_id, CLIENT_ID_SIZE); ipport_copy(&ping->to_ping[(i + r) % MAX_TO_PING].ip_port, &ip_port); return 0; } } return -1; } /* Ping all the valid nodes in the to_ping list every TIME_TO_PING seconds. * This function must be run at least once every TIME_TO_PING seconds. */ void do_to_ping(PING *ping) { if (!is_timeout(ping->last_to_ping, TIME_TO_PING)) return; ping->last_to_ping = unix_time(); uint32_t i; for (i = 0; i < MAX_TO_PING; ++i) { if (!ip_isset(&ping->to_ping[i].ip_port.ip)) return; send_ping_request(ping, ping->to_ping[i].ip_port, ping->to_ping[i].client_id); ip_reset(&ping->to_ping[i].ip_port.ip); } } PING *new_ping(DHT *dht) { PING *ping = calloc(1, sizeof(PING)); if (ping == NULL) return NULL; ping->dht = dht; networking_registerhandler(ping->dht->net, NET_PACKET_PING_REQUEST, &handle_ping_request, dht); networking_registerhandler(ping->dht->net, NET_PACKET_PING_RESPONSE, &handle_ping_response, dht); return ping; } void kill_ping(PING *ping) { networking_registerhandler(ping->dht->net, NET_PACKET_PING_REQUEST, NULL, NULL); networking_registerhandler(ping->dht->net, NET_PACKET_PING_RESPONSE, NULL, NULL); free(ping); }