261d2e53b7
11ab1d2a723 fix: reduce memory usage in group chats by 75% Significantly reduced the memory usage of groups since all message slots are preallocated for every peer for send and receive buffers of buffer size (hundreds of MiB peak when save contained alot of peers to try to connect to) 4f09f4e147c chore: Fix tsan build by moving it to GitHub CI. 6460c25c9e0 refactor: Use `merge_sort` instead of `qsort` for sorting. c660bbe8c95 test: Fix crypto_test to initialise its plain text buffer. 0204db6184b cleanup: Fix layering check warnings. df2211e1548 refactor: Use tox memory allocator for temporary buffers in crypto. ac812871a2e feat: implement the last 2 missing network struct functions and make use of them 29d1043be0b test: friend request test now tests min/max message sizes 93aafd78c1f fix: friend requests with very long messages are no longer dropped 819aa2b2618 feat: Add option to disable DNS lookups in toxcore. 0ac23cee035 fix: windows use of REUSEADDR 7d2811d302d chore(ci): make bazel server shutdown faster 1dc399ba20d chore: Use vcpkg instead of conan in the MSVC build. 14d823165d9 chore: Migrate to conan 2. bdd17c16787 cleanup: Allocate logger using tox memory allocator. b396c061515 chore(deps): bump third_party/cmp from `2ac6bca` to `52bfcfa` 2e94da60d09 feat(net): add missing connect to network struct 41fb1839c7b chore: Add check to ensure version numbers agree. 934a8301113 chore: Release 0.2.20 3acef4bf044 fix: Add missing free in dht_get_nodes_response event. git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: 11ab1d2a7232eee19b51ce126ccce267d6578903
749 lines
27 KiB
C
749 lines
27 KiB
C
/* SPDX-License-Identifier: GPL-3.0-or-later
|
|
* Copyright © 2016-2018 The TokTok team.
|
|
* Copyright © 2013 Tox project.
|
|
*/
|
|
|
|
/**
|
|
* Implementation of the announce part of docs/Prevent_Tracking.txt
|
|
*/
|
|
#include "onion_announce.h"
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "DHT.h"
|
|
#include "LAN_discovery.h"
|
|
#include "attributes.h"
|
|
#include "ccompat.h"
|
|
#include "crypto_core.h"
|
|
#include "logger.h"
|
|
#include "mem.h"
|
|
#include "mono_time.h"
|
|
#include "network.h"
|
|
#include "onion.h"
|
|
#include "shared_key_cache.h"
|
|
#include "sort.h"
|
|
#include "timed_auth.h"
|
|
|
|
#define PING_ID_TIMEOUT ONION_ANNOUNCE_TIMEOUT
|
|
|
|
#define ANNOUNCE_REQUEST_MIN_SIZE_RECV (ONION_ANNOUNCE_REQUEST_MIN_SIZE + ONION_RETURN_3)
|
|
#define ANNOUNCE_REQUEST_MAX_SIZE_RECV (ONION_ANNOUNCE_REQUEST_MAX_SIZE + ONION_RETURN_3)
|
|
|
|
/* TODO(Jfreegman): DEPRECATE */
|
|
#define ANNOUNCE_REQUEST_SIZE_RECV (ONION_ANNOUNCE_REQUEST_SIZE + ONION_RETURN_3)
|
|
|
|
#define DATA_REQUEST_MIN_SIZE ONION_DATA_REQUEST_MIN_SIZE
|
|
#define DATA_REQUEST_MIN_SIZE_RECV (DATA_REQUEST_MIN_SIZE + ONION_RETURN_3)
|
|
|
|
#define ONION_MINIMAL_SIZE (ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE * 2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH)
|
|
|
|
/* Settings for the shared key cache */
|
|
#define MAX_KEYS_PER_SLOT 4
|
|
#define KEYS_TIMEOUT 600
|
|
|
|
static_assert(ONION_PING_ID_SIZE == CRYPTO_PUBLIC_KEY_SIZE,
|
|
"announce response packets assume that ONION_PING_ID_SIZE is equal to CRYPTO_PUBLIC_KEY_SIZE");
|
|
|
|
typedef struct Onion_Announce_Entry {
|
|
uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE];
|
|
IP_Port ret_ip_port;
|
|
uint8_t ret[ONION_RETURN_3];
|
|
uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint64_t announce_time;
|
|
} Onion_Announce_Entry;
|
|
|
|
struct Onion_Announce {
|
|
const Logger *log;
|
|
const Mono_Time *mono_time;
|
|
const Random *rng;
|
|
const Memory *mem;
|
|
DHT *dht;
|
|
Networking_Core *net;
|
|
Onion_Announce_Entry entries[ONION_ANNOUNCE_MAX_ENTRIES];
|
|
uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE];
|
|
|
|
Shared_Key_Cache *shared_keys_recv;
|
|
|
|
uint16_t extra_data_max_size;
|
|
pack_extra_data_cb *extra_data_callback;
|
|
void *extra_data_object;
|
|
};
|
|
|
|
void onion_announce_extra_data_callback(Onion_Announce *onion_a, uint16_t extra_data_max_size,
|
|
pack_extra_data_cb *extra_data_callback, void *extra_data_object)
|
|
{
|
|
onion_a->extra_data_max_size = extra_data_max_size;
|
|
onion_a->extra_data_callback = extra_data_callback;
|
|
onion_a->extra_data_object = extra_data_object;
|
|
}
|
|
|
|
uint8_t *onion_announce_entry_public_key(Onion_Announce *onion_a, uint32_t entry)
|
|
{
|
|
return onion_a->entries[entry].public_key;
|
|
}
|
|
|
|
void onion_announce_entry_set_time(Onion_Announce *onion_a, uint32_t entry, uint64_t announce_time)
|
|
{
|
|
onion_a->entries[entry].announce_time = announce_time;
|
|
}
|
|
|
|
/** @brief Create an onion announce request packet in packet of max_packet_length.
|
|
*
|
|
* Recommended value for max_packet_length is ONION_ANNOUNCE_REQUEST_MIN_SIZE.
|
|
*
|
|
* dest_client_id is the public key of the node the packet will be sent to.
|
|
* public_key and secret_key is the kepair which will be used to encrypt the request.
|
|
* ping_id is the ping id that will be sent in the request.
|
|
* client_id is the client id of the node we are searching for.
|
|
* data_public_key is the public key we want others to encrypt their data packets with.
|
|
* sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to
|
|
* receive back in the response.
|
|
*
|
|
* return -1 on failure.
|
|
* return packet length on success.
|
|
*/
|
|
int create_announce_request(const Memory *mem, const Random *rng, uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id,
|
|
const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id,
|
|
const uint8_t *data_public_key, uint64_t sendback_data)
|
|
{
|
|
if (max_packet_length < ONION_ANNOUNCE_REQUEST_MIN_SIZE) {
|
|
return -1;
|
|
}
|
|
|
|
uint8_t plain[ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE +
|
|
ONION_ANNOUNCE_SENDBACK_DATA_LENGTH];
|
|
memcpy(plain, ping_id, ONION_PING_ID_SIZE);
|
|
memcpy(plain + ONION_PING_ID_SIZE, client_id, CRYPTO_PUBLIC_KEY_SIZE);
|
|
memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE, data_public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, &sendback_data,
|
|
sizeof(sendback_data));
|
|
|
|
packet[0] = NET_PACKET_ANNOUNCE_REQUEST_OLD;
|
|
random_nonce(rng, packet + 1);
|
|
|
|
const int len = encrypt_data(mem, dest_client_id, secret_key, packet + 1, plain, sizeof(plain),
|
|
packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE);
|
|
|
|
if ((uint32_t)len + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE != ONION_ANNOUNCE_REQUEST_MIN_SIZE) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(packet + 1 + CRYPTO_NONCE_SIZE, public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
|
|
return ONION_ANNOUNCE_REQUEST_MIN_SIZE;
|
|
}
|
|
|
|
/** @brief Create an onion data request packet in packet of max_packet_length.
|
|
*
|
|
* Recommended value for max_packet_length is ONION_ANNOUNCE_REQUEST_SIZE.
|
|
*
|
|
* public_key is the real public key of the node which we want to send the data of length length to.
|
|
* encrypt_public_key is the public key used to encrypt the data packet.
|
|
*
|
|
* nonce is the nonce to encrypt this packet with
|
|
*
|
|
* return -1 on failure.
|
|
* return 0 on success.
|
|
*/
|
|
int create_data_request(const Memory *mem, const Random *rng, uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key,
|
|
const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length)
|
|
{
|
|
if (DATA_REQUEST_MIN_SIZE + length > max_packet_length) {
|
|
return -1;
|
|
}
|
|
|
|
if (DATA_REQUEST_MIN_SIZE + length > ONION_MAX_DATA_SIZE) {
|
|
return -1;
|
|
}
|
|
|
|
packet[0] = NET_PACKET_ONION_DATA_REQUEST;
|
|
memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE);
|
|
|
|
uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE];
|
|
crypto_new_keypair(rng, random_public_key, random_secret_key);
|
|
|
|
memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, random_public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
|
|
const int len = encrypt_data(mem, encrypt_public_key, random_secret_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length,
|
|
packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE);
|
|
|
|
if (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len != DATA_REQUEST_MIN_SIZE +
|
|
length) {
|
|
return -1;
|
|
}
|
|
|
|
return DATA_REQUEST_MIN_SIZE + length;
|
|
}
|
|
|
|
/** @brief Create and send an onion announce request packet.
|
|
*
|
|
* path is the path the request will take before it is sent to dest.
|
|
*
|
|
* public_key and secret_key is the kepair which will be used to encrypt the request.
|
|
* ping_id is the ping id that will be sent in the request.
|
|
* client_id is the client id of the node we are searching for.
|
|
* data_public_key is the public key we want others to encrypt their data packets with.
|
|
* sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to
|
|
* receive back in the response.
|
|
*
|
|
* return -1 on failure.
|
|
* return 0 on success.
|
|
*/
|
|
int send_announce_request(
|
|
const Logger *log, const Memory *mem, const Networking_Core *net, const Random *rng,
|
|
const Onion_Path *path, const Node_format *dest,
|
|
const uint8_t *public_key, const uint8_t *secret_key,
|
|
const uint8_t *ping_id, const uint8_t *client_id,
|
|
const uint8_t *data_public_key, uint64_t sendback_data)
|
|
{
|
|
uint8_t request[ONION_ANNOUNCE_REQUEST_MIN_SIZE];
|
|
int len = create_announce_request(mem, rng, request, sizeof(request), dest->public_key, public_key, secret_key, ping_id,
|
|
client_id, data_public_key, sendback_data);
|
|
|
|
if (len != sizeof(request)) {
|
|
return -1;
|
|
}
|
|
|
|
uint8_t packet[ONION_MAX_PACKET_SIZE];
|
|
len = create_onion_packet(mem, rng, packet, sizeof(packet), path, &dest->ip_port, request, sizeof(request));
|
|
|
|
if (len == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (sendpacket(net, &path->ip_port1, packet, len) != len) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Create and send an onion data request packet.
|
|
*
|
|
* path is the path the request will take before it is sent to dest.
|
|
* (if dest knows the person with the public_key they should
|
|
* send the packet to that person in the form of a response)
|
|
*
|
|
* public_key is the real public key of the node which we want to send the data of length length to.
|
|
* encrypt_public_key is the public key used to encrypt the data packet.
|
|
*
|
|
* nonce is the nonce to encrypt this packet with
|
|
*
|
|
* The maximum length of data is MAX_DATA_REQUEST_SIZE.
|
|
*
|
|
* return -1 on failure.
|
|
* return 0 on success.
|
|
*/
|
|
int send_data_request(
|
|
const Logger *log, const Memory *mem, const Networking_Core *net, const Random *rng, const Onion_Path *path, const IP_Port *dest,
|
|
const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce,
|
|
const uint8_t *data, uint16_t length)
|
|
{
|
|
uint8_t request[ONION_MAX_DATA_SIZE];
|
|
int len = create_data_request(mem, rng, request, sizeof(request), public_key, encrypt_public_key, nonce, data, length);
|
|
|
|
if (len == -1) {
|
|
return -1;
|
|
}
|
|
|
|
uint8_t packet[ONION_MAX_PACKET_SIZE];
|
|
len = create_onion_packet(mem, rng, packet, sizeof(packet), path, dest, request, len);
|
|
|
|
if (len == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (sendpacket(net, &path->ip_port1, packet, len) != len) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief check if public key is in entries list
|
|
*
|
|
* return -1 if no
|
|
* return position in list if yes
|
|
*/
|
|
non_null()
|
|
static int in_entries(const Onion_Announce *onion_a, const uint8_t *public_key)
|
|
{
|
|
for (unsigned int i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) {
|
|
if (!mono_time_is_timeout(onion_a->mono_time, onion_a->entries[i].announce_time, ONION_ANNOUNCE_TIMEOUT)
|
|
&& pk_equal(onion_a->entries[i].public_key, public_key)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
typedef struct Onion_Announce_Entry_Cmp {
|
|
const Memory *mem;
|
|
const Mono_Time *mono_time;
|
|
const uint8_t *comp_public_key;
|
|
} Onion_Announce_Entry_Cmp;
|
|
|
|
non_null()
|
|
static int onion_announce_entry_cmp(const Onion_Announce_Entry_Cmp *cmp, const Onion_Announce_Entry *entry1, const Onion_Announce_Entry *entry2)
|
|
{
|
|
const bool t1 = mono_time_is_timeout(cmp->mono_time, entry1->announce_time, ONION_ANNOUNCE_TIMEOUT);
|
|
const bool t2 = mono_time_is_timeout(cmp->mono_time, entry2->announce_time, ONION_ANNOUNCE_TIMEOUT);
|
|
|
|
if (t1 && t2) {
|
|
return 0;
|
|
}
|
|
|
|
if (t1) {
|
|
return -1;
|
|
}
|
|
|
|
if (t2) {
|
|
return 1;
|
|
}
|
|
|
|
const int closest = id_closest(cmp->comp_public_key, entry1->public_key, entry2->public_key);
|
|
|
|
if (closest == 1) {
|
|
return 1;
|
|
}
|
|
|
|
if (closest == 2) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
non_null()
|
|
static bool onion_announce_entry_less_handler(const void *object, const void *a, const void *b)
|
|
{
|
|
const Onion_Announce_Entry_Cmp *cmp = (const Onion_Announce_Entry_Cmp *)object;
|
|
const Onion_Announce_Entry *entry1 = (const Onion_Announce_Entry *)a;
|
|
const Onion_Announce_Entry *entry2 = (const Onion_Announce_Entry *)b;
|
|
|
|
return onion_announce_entry_cmp(cmp, entry1, entry2) < 0;
|
|
}
|
|
|
|
non_null()
|
|
static const void *onion_announce_entry_get_handler(const void *arr, uint32_t index)
|
|
{
|
|
const Onion_Announce_Entry *entries = (const Onion_Announce_Entry *)arr;
|
|
return &entries[index];
|
|
}
|
|
|
|
non_null()
|
|
static void onion_announce_entry_set_handler(void *arr, uint32_t index, const void *val)
|
|
{
|
|
Onion_Announce_Entry *entries = (Onion_Announce_Entry *)arr;
|
|
const Onion_Announce_Entry *entry = (const Onion_Announce_Entry *)val;
|
|
entries[index] = *entry;
|
|
}
|
|
|
|
non_null()
|
|
static void *onion_announce_entry_subarr_handler(void *arr, uint32_t index, uint32_t size)
|
|
{
|
|
Onion_Announce_Entry *entries = (Onion_Announce_Entry *)arr;
|
|
return &entries[index];
|
|
}
|
|
|
|
non_null()
|
|
static void *onion_announce_entry_alloc_handler(const void *object, uint32_t size)
|
|
{
|
|
const Onion_Announce_Entry_Cmp *cmp = (const Onion_Announce_Entry_Cmp *)object;
|
|
Onion_Announce_Entry *tmp = (Onion_Announce_Entry *)mem_valloc(cmp->mem, size, sizeof(Onion_Announce_Entry));
|
|
|
|
if (tmp == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
non_null()
|
|
static void onion_announce_entry_delete_handler(const void *object, void *arr, uint32_t size)
|
|
{
|
|
const Onion_Announce_Entry_Cmp *cmp = (const Onion_Announce_Entry_Cmp *)object;
|
|
mem_delete(cmp->mem, arr);
|
|
}
|
|
|
|
static const Sort_Funcs onion_announce_entry_cmp_funcs = {
|
|
onion_announce_entry_less_handler,
|
|
onion_announce_entry_get_handler,
|
|
onion_announce_entry_set_handler,
|
|
onion_announce_entry_subarr_handler,
|
|
onion_announce_entry_alloc_handler,
|
|
onion_announce_entry_delete_handler,
|
|
};
|
|
|
|
non_null()
|
|
static void sort_onion_announce_list(const Memory *mem, const Mono_Time *mono_time,
|
|
Onion_Announce_Entry *list, unsigned int length,
|
|
const uint8_t *comp_public_key)
|
|
{
|
|
// Pass comp_public_key to sort with each Onion_Announce_Entry entry, so the
|
|
// comparison function can use it as the base of comparison.
|
|
const Onion_Announce_Entry_Cmp cmp = {
|
|
mem,
|
|
mono_time,
|
|
comp_public_key,
|
|
};
|
|
|
|
merge_sort(list, length, &cmp, &onion_announce_entry_cmp_funcs);
|
|
}
|
|
|
|
/** @brief add entry to entries list
|
|
*
|
|
* return -1 if failure
|
|
* return position if added
|
|
*/
|
|
non_null()
|
|
static int add_to_entries(Onion_Announce *onion_a, const IP_Port *ret_ip_port, const uint8_t *public_key,
|
|
const uint8_t *data_public_key, const uint8_t *ret)
|
|
{
|
|
int pos = in_entries(onion_a, public_key);
|
|
|
|
if (pos == -1) {
|
|
for (unsigned i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) {
|
|
if (mono_time_is_timeout(onion_a->mono_time, onion_a->entries[i].announce_time, ONION_ANNOUNCE_TIMEOUT)) {
|
|
pos = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pos == -1) {
|
|
if (id_closest(dht_get_self_public_key(onion_a->dht), public_key, onion_a->entries[0].public_key) == 1) {
|
|
pos = 0;
|
|
}
|
|
}
|
|
|
|
if (pos == -1) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(onion_a->entries[pos].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
onion_a->entries[pos].ret_ip_port = *ret_ip_port;
|
|
memcpy(onion_a->entries[pos].ret, ret, ONION_RETURN_3);
|
|
memcpy(onion_a->entries[pos].data_public_key, data_public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
onion_a->entries[pos].announce_time = mono_time_get(onion_a->mono_time);
|
|
|
|
sort_onion_announce_list(onion_a->mem, onion_a->mono_time,
|
|
onion_a->entries, ONION_ANNOUNCE_MAX_ENTRIES,
|
|
dht_get_self_public_key(onion_a->dht));
|
|
return in_entries(onion_a, public_key);
|
|
}
|
|
|
|
non_null()
|
|
static void make_announce_payload_helper(const Onion_Announce *onion_a, const uint8_t *ping_id,
|
|
uint8_t *response, int index, const uint8_t *packet_public_key, const uint8_t *data_public_key)
|
|
{
|
|
if (index < 0) {
|
|
response[0] = 0;
|
|
memcpy(response + 1, ping_id, ONION_PING_ID_SIZE);
|
|
return;
|
|
}
|
|
|
|
if (pk_equal(onion_a->entries[index].public_key, packet_public_key)) {
|
|
if (!pk_equal(onion_a->entries[index].data_public_key, data_public_key)) {
|
|
response[0] = 0;
|
|
memcpy(response + 1, ping_id, ONION_PING_ID_SIZE);
|
|
} else {
|
|
response[0] = 2;
|
|
memcpy(response + 1, ping_id, ONION_PING_ID_SIZE);
|
|
}
|
|
} else {
|
|
response[0] = 1;
|
|
memcpy(response + 1, onion_a->entries[index].data_public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
}
|
|
}
|
|
|
|
/** @brief Handle an onion announce request, possibly with extra data for group chats.
|
|
*
|
|
* @param onion_a The announce object.
|
|
* @param source Requester IP/Port.
|
|
* @param packet Encrypted incoming packet.
|
|
* @param length Length of incoming packet.
|
|
* @param response_packet_id Packet ID to use for the onion announce response.
|
|
* @param plain_size Expected size of the decrypted packet. This function returns an error if the
|
|
* actual decrypted size is not exactly equal to this number.
|
|
* @param want_node_count If true, the packed nodes in the response are preceded by the number of
|
|
* nodes sent in the packet. This is necessary if you want to send extra data after the nodes.
|
|
* @param max_extra_size Amount of memory to allocate in the outgoing packet to be filled by the
|
|
* extra data callback.
|
|
* @param pack_extra_data_callback Callback that may write extra data into the packet.
|
|
*
|
|
* @retval 1 on failure.
|
|
* @retval 0 on success.
|
|
*/
|
|
non_null(1, 2, 3) nullable(9)
|
|
static int handle_announce_request_common(
|
|
Onion_Announce *onion_a, const IP_Port *source, const uint8_t *packet, uint16_t length,
|
|
uint8_t response_packet_id, uint16_t plain_size, bool want_node_count, uint16_t max_extra_size,
|
|
pack_extra_data_cb *pack_extra_data_callback)
|
|
{
|
|
const uint8_t *packet_public_key = packet + 1 + CRYPTO_NONCE_SIZE;
|
|
const uint8_t *shared_key = shared_key_cache_lookup(onion_a->shared_keys_recv, packet_public_key);
|
|
|
|
if (shared_key == nullptr) {
|
|
/* Error looking up/deriving the shared key */
|
|
return 1;
|
|
}
|
|
|
|
uint8_t *plain = (uint8_t *)mem_balloc(onion_a->mem, plain_size);
|
|
|
|
if (plain == nullptr) {
|
|
return 1;
|
|
}
|
|
|
|
const int decrypted_len = decrypt_data_symmetric(onion_a->mem, shared_key, packet + 1,
|
|
packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, plain_size + CRYPTO_MAC_SIZE, plain);
|
|
|
|
if ((uint32_t)decrypted_len != plain_size) {
|
|
mem_delete(onion_a->mem, plain);
|
|
return 1;
|
|
}
|
|
|
|
const uint16_t ping_id_data_len = CRYPTO_PUBLIC_KEY_SIZE + sizeof(*source);
|
|
uint8_t ping_id_data[CRYPTO_PUBLIC_KEY_SIZE + sizeof(*source)];
|
|
memcpy(ping_id_data, packet_public_key, CRYPTO_PUBLIC_KEY_SIZE);
|
|
memcpy(ping_id_data + CRYPTO_PUBLIC_KEY_SIZE, source, sizeof(*source));
|
|
|
|
const uint8_t *data_public_key = plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE;
|
|
|
|
int index;
|
|
|
|
if (check_timed_auth(onion_a->mono_time, PING_ID_TIMEOUT, onion_a->hmac_key,
|
|
ping_id_data, ping_id_data_len, plain)) {
|
|
index = add_to_entries(onion_a, source, packet_public_key, data_public_key,
|
|
packet + (length - ONION_RETURN_3));
|
|
} else {
|
|
index = in_entries(onion_a, plain + ONION_PING_ID_SIZE);
|
|
}
|
|
|
|
/* Respond with a announce response packet */
|
|
Node_format nodes_list[MAX_SENT_NODES];
|
|
const unsigned int num_nodes =
|
|
get_close_nodes(onion_a->dht, plain + ONION_PING_ID_SIZE, nodes_list, net_family_unspec(), ip_is_lan(&source->ip), false);
|
|
|
|
assert(num_nodes <= UINT8_MAX);
|
|
|
|
uint8_t nonce[CRYPTO_NONCE_SIZE];
|
|
random_nonce(onion_a->rng, nonce);
|
|
|
|
const uint16_t nodes_offset = 1 + ONION_PING_ID_SIZE + (want_node_count ? 1 : 0);
|
|
const uint16_t response_size = nodes_offset
|
|
+ MAX_SENT_NODES * PACKED_NODE_SIZE_IP6
|
|
+ max_extra_size;
|
|
uint8_t *response = (uint8_t *)mem_balloc(onion_a->mem, response_size);
|
|
|
|
if (response == nullptr) {
|
|
mem_delete(onion_a->mem, plain);
|
|
return 1;
|
|
}
|
|
|
|
uint8_t ping_id[TIMED_AUTH_SIZE];
|
|
generate_timed_auth(onion_a->mono_time, PING_ID_TIMEOUT, onion_a->hmac_key,
|
|
ping_id_data, ping_id_data_len, ping_id);
|
|
|
|
make_announce_payload_helper(onion_a, ping_id, response, index, packet_public_key, data_public_key);
|
|
|
|
int nodes_length = 0;
|
|
|
|
if (num_nodes != 0) {
|
|
nodes_length = pack_nodes(onion_a->log, response + nodes_offset, sizeof(nodes_list), nodes_list,
|
|
(uint16_t)num_nodes);
|
|
|
|
if (nodes_length <= 0) {
|
|
LOGGER_WARNING(onion_a->log, "Failed to pack nodes");
|
|
mem_delete(onion_a->mem, response);
|
|
mem_delete(onion_a->mem, plain);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
uint16_t offset = nodes_offset + nodes_length;
|
|
|
|
if (want_node_count) {
|
|
response[1 + ONION_PING_ID_SIZE] = (uint8_t)num_nodes;
|
|
}
|
|
|
|
const int extra_size = pack_extra_data_callback == nullptr ? 0
|
|
: pack_extra_data_callback(onion_a->extra_data_object,
|
|
onion_a->log, onion_a->mono_time, num_nodes,
|
|
plain + ONION_MINIMAL_SIZE, length - ANNOUNCE_REQUEST_MIN_SIZE_RECV,
|
|
response, response_size, offset);
|
|
|
|
if (extra_size == -1) {
|
|
mem_delete(onion_a->mem, response);
|
|
mem_delete(onion_a->mem, plain);
|
|
return 1;
|
|
}
|
|
|
|
offset += extra_size;
|
|
|
|
uint8_t data[ONION_ANNOUNCE_RESPONSE_MAX_SIZE];
|
|
const int len = encrypt_data_symmetric(onion_a->mem, shared_key, nonce, response, offset,
|
|
data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE);
|
|
|
|
if (len != offset + CRYPTO_MAC_SIZE) {
|
|
LOGGER_ERROR(onion_a->log, "Failed to encrypt announce response");
|
|
mem_delete(onion_a->mem, response);
|
|
mem_delete(onion_a->mem, plain);
|
|
return 1;
|
|
}
|
|
|
|
data[0] = response_packet_id;
|
|
memcpy(data + 1, plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE,
|
|
ONION_ANNOUNCE_SENDBACK_DATA_LENGTH);
|
|
memcpy(data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, nonce, CRYPTO_NONCE_SIZE);
|
|
|
|
if (send_onion_response(onion_a->log, onion_a->net, source, data,
|
|
1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + len,
|
|
packet + (length - ONION_RETURN_3)) == -1) {
|
|
mem_delete(onion_a->mem, response);
|
|
mem_delete(onion_a->mem, plain);
|
|
return 1;
|
|
}
|
|
|
|
mem_delete(onion_a->mem, response);
|
|
mem_delete(onion_a->mem, plain);
|
|
return 0;
|
|
}
|
|
|
|
non_null()
|
|
static int handle_gca_announce_request(Onion_Announce *onion_a, const IP_Port *source, const uint8_t *packet,
|
|
uint16_t length)
|
|
{
|
|
if (length > ANNOUNCE_REQUEST_MAX_SIZE_RECV || length <= ANNOUNCE_REQUEST_MIN_SIZE_RECV) {
|
|
return 1;
|
|
}
|
|
|
|
if (onion_a->extra_data_callback == nullptr) {
|
|
return 1;
|
|
}
|
|
|
|
return handle_announce_request_common(onion_a, source, packet, length, NET_PACKET_ANNOUNCE_RESPONSE,
|
|
ONION_MINIMAL_SIZE + length - ANNOUNCE_REQUEST_MIN_SIZE_RECV,
|
|
true, onion_a->extra_data_max_size, onion_a->extra_data_callback);
|
|
}
|
|
|
|
non_null(1, 2, 3) nullable(5)
|
|
static int handle_announce_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length,
|
|
void *userdata)
|
|
{
|
|
Onion_Announce *onion_a = (Onion_Announce *)object;
|
|
|
|
if (length != ANNOUNCE_REQUEST_MIN_SIZE_RECV) {
|
|
return handle_gca_announce_request(onion_a, source, packet, length);
|
|
}
|
|
|
|
return handle_announce_request_common(onion_a, source, packet, length, NET_PACKET_ANNOUNCE_RESPONSE,
|
|
ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE * 2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH,
|
|
true, 0, nullptr);
|
|
}
|
|
|
|
/* TODO(Jfreegman): DEPRECATE */
|
|
non_null(1, 2, 3) nullable(5)
|
|
static int handle_announce_request_old(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length,
|
|
void *userdata)
|
|
{
|
|
Onion_Announce *onion_a = (Onion_Announce *)object;
|
|
|
|
if (length != ANNOUNCE_REQUEST_SIZE_RECV) {
|
|
return 1;
|
|
}
|
|
|
|
return handle_announce_request_common(onion_a, source, packet, length, NET_PACKET_ANNOUNCE_RESPONSE_OLD,
|
|
ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE * 2 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH,
|
|
false, 0, nullptr);
|
|
}
|
|
|
|
non_null()
|
|
static int handle_data_request(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length,
|
|
void *userdata)
|
|
{
|
|
const Onion_Announce *onion_a = (const Onion_Announce *)object;
|
|
|
|
if (length <= DATA_REQUEST_MIN_SIZE_RECV) {
|
|
return 1;
|
|
}
|
|
|
|
if (length > ONION_MAX_PACKET_SIZE) {
|
|
return 1;
|
|
}
|
|
|
|
const int index = in_entries(onion_a, packet + 1);
|
|
|
|
if (index == -1) {
|
|
return 1;
|
|
}
|
|
|
|
const uint16_t data_size = length - (CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3);
|
|
VLA(uint8_t, data, data_size);
|
|
data[0] = NET_PACKET_ONION_DATA_RESPONSE;
|
|
memcpy(data + 1, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3));
|
|
|
|
if (send_onion_response(onion_a->log, onion_a->net, &onion_a->entries[index].ret_ip_port, data, data_size,
|
|
onion_a->entries[index].ret) == -1) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Onion_Announce *new_onion_announce(const Logger *log, const Memory *mem, const Random *rng, const Mono_Time *mono_time, DHT *dht)
|
|
{
|
|
if (dht == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
Onion_Announce *onion_a = (Onion_Announce *)mem_alloc(mem, sizeof(Onion_Announce));
|
|
|
|
if (onion_a == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
onion_a->log = log;
|
|
onion_a->rng = rng;
|
|
onion_a->mem = mem;
|
|
onion_a->mono_time = mono_time;
|
|
onion_a->dht = dht;
|
|
onion_a->net = dht_get_net(dht);
|
|
onion_a->extra_data_max_size = 0;
|
|
onion_a->extra_data_callback = nullptr;
|
|
onion_a->extra_data_object = nullptr;
|
|
new_hmac_key(rng, onion_a->hmac_key);
|
|
|
|
onion_a->shared_keys_recv = shared_key_cache_new(log, mono_time, mem, dht_get_self_secret_key(dht), KEYS_TIMEOUT, MAX_KEYS_PER_SLOT);
|
|
if (onion_a->shared_keys_recv == nullptr) {
|
|
// cppcheck-suppress mismatchAllocDealloc
|
|
kill_onion_announce(onion_a);
|
|
return nullptr;
|
|
}
|
|
|
|
networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_announce_request, onion_a);
|
|
networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST_OLD, &handle_announce_request_old, onion_a);
|
|
networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, &handle_data_request, onion_a);
|
|
|
|
return onion_a;
|
|
}
|
|
|
|
void kill_onion_announce(Onion_Announce *onion_a)
|
|
{
|
|
if (onion_a == nullptr) {
|
|
return;
|
|
}
|
|
|
|
networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, nullptr, nullptr);
|
|
networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST_OLD, nullptr, nullptr);
|
|
networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, nullptr, nullptr);
|
|
|
|
crypto_memzero(onion_a->hmac_key, CRYPTO_HMAC_KEY_SIZE);
|
|
shared_key_cache_free(onion_a->shared_keys_recv);
|
|
|
|
mem_delete(onion_a->mem, onion_a);
|
|
}
|