From b2315c50e0838687cd328838f1475be095b358f4 Mon Sep 17 00:00:00 2001 From: jfreegman Date: Tue, 19 Dec 2023 11:07:03 -0500 Subject: [PATCH] Add groupchat API function that returns an IP address string for a peer This function will return an IP address string associated with a peer. If the peer is not accepting direct connections a placeholder value will be returned, indicating that their real IP address is unknown. We do not return TCP relay IP addresses because a TCP connection with a peer may use multiple relays simultaneously. --- auto_tests/group_general_test.c | 26 +++++++++ .../docker/tox-bootstrapd.sha256 | 2 +- toxcore/group_chats.c | 57 +++++++++++++++++++ toxcore/group_chats.h | 23 ++++++++ toxcore/network.c | 15 +++-- toxcore/network.h | 7 ++- toxcore/tox.c | 2 + toxcore/tox_api.c | 4 ++ toxcore/tox_private.c | 55 ++++++++++++++++++ toxcore/tox_private.h | 45 +++++++++++++++ 10 files changed, 225 insertions(+), 11 deletions(-) diff --git a/auto_tests/group_general_test.c b/auto_tests/group_general_test.c index 1a506ab4..328e76f3 100644 --- a/auto_tests/group_general_test.c +++ b/auto_tests/group_general_test.c @@ -9,6 +9,7 @@ #include #include "auto_test_support.h" +#include "../toxcore/tox_private.h" typedef struct State { size_t peer_joined_count; @@ -42,6 +43,22 @@ typedef struct State { #define PEER_LIMIT 20 +static void print_ip(Tox *tox, uint32_t groupnumber, uint32_t peer_id) +{ + Tox_Err_Group_Peer_Query err; + size_t length = tox_group_peer_get_ip_address_size(tox, groupnumber, peer_id, &err); + + ck_assert_msg(err == TOX_ERR_GROUP_PEER_QUERY_OK, "failed to get ip address size: error %d", err); + + uint8_t ip_str[TOX_GROUP_PEER_IP_STRING_MAX_LENGTH]; + tox_group_peer_get_ip_address(tox, groupnumber, peer_id, ip_str, &err); + ip_str[length] = '\0'; + + ck_assert_msg(err == TOX_ERR_GROUP_PEER_QUERY_OK, "failed to get ip address: error %d", err); + + fprintf(stderr, "%s\n", ip_str); +} + static bool all_group_peers_connected(AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, size_t name_length) { for (size_t i = 0; i < tox_count; ++i) { @@ -119,6 +136,9 @@ static void group_peer_join_handler(Tox *tox, uint32_t groupnumber, uint32_t pee } } + fprintf(stderr, "%s joined with IP: ", peer_name); + print_ip(tox, groupnumber, peer_id); + state->peer_id = peer_id; ++state->peer_joined_count; } @@ -178,6 +198,11 @@ static void group_peer_self_join_handler(Tox *tox, uint32_t groupnumber, void *u ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", query_err); ck_assert(memcmp(topic, TOPIC, TOPIC_LEN) == 0); + uint32_t peer_id = tox_group_self_get_peer_id(tox, groupnumber, nullptr); + + fprintf(stderr, "self joined with IP: "); + print_ip(tox, groupnumber, peer_id); + ++state->self_joined_count; } @@ -341,6 +366,7 @@ static void group_announce_test(AutoTox *autotoxes) ck_assert(memcmp(tox0_pk_query, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0); fprintf(stderr, "Peer 0 disconnecting...\n"); + // tox 0 disconnects then reconnects Tox_Err_Group_Disconnect d_err; tox_group_disconnect(tox0, groupnumber, &d_err); diff --git a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 index e6324799..2ed9bd8e 100644 --- a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 +++ b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 @@ -1 +1 @@ -e0fa59db7d25204f917e4b114e1607cb3819fe9da74de5f9e807bcf76abefe42 /usr/local/bin/tox-bootstrapd +8cb8b2f7bbc0ce71551365ed72ae468c731eafdce93e01d8c9a04ed5d904fcd9 /usr/local/bin/tox-bootstrapd diff --git a/toxcore/group_chats.c b/toxcore/group_chats.c index 311960de..f3731108 100644 --- a/toxcore/group_chats.c +++ b/toxcore/group_chats.c @@ -3517,6 +3517,63 @@ int gc_get_peer_public_key_by_peer_id(const GC_Chat *chat, uint32_t peer_id, uin return 0; } +/** @brief Puts a string of the IP associated with `ip_port` in `ip_str` if the + * connection is direct, otherwise puts a placeholder in the buffer indicating that + * the IP cannot be displayed. + */ +non_null() +static void get_gc_ip_ntoa(const IP_Port *ip_port, Ip_Ntoa *ip_str) +{ + net_ip_ntoa(&ip_port->ip, ip_str); + + if (!ip_str->ip_is_valid) { + ip_str->buf[0] = '-'; + ip_str->buf[1] = '\0'; + ip_str->length = 1; + } +} + +int gc_get_peer_ip_address_size(const GC_Chat *chat, uint32_t peer_id) +{ + const int peer_number = get_peer_number_of_peer_id(chat, peer_id); + const GC_Connection *gconn = get_gc_connection(chat, peer_number); + + if (gconn == nullptr) { + return -1; + } + + const IP_Port *ip_port = peer_number == 0 ? &chat->self_ip_port : &gconn->addr.ip_port; + + Ip_Ntoa ip_str; + get_gc_ip_ntoa(ip_port, &ip_str); + + return ip_str.length; +} + +int gc_get_peer_ip_address(const GC_Chat *chat, uint32_t peer_id, uint8_t *ip_addr) +{ + const int peer_number = get_peer_number_of_peer_id(chat, peer_id); + const GC_Connection *gconn = get_gc_connection(chat, peer_number); + + if (gconn == nullptr) { + return -1; + } + + if (ip_addr == nullptr) { + return -2; + } + + const IP_Port *ip_port = peer_number == 0 ? &chat->self_ip_port : &gconn->addr.ip_port; + + Ip_Ntoa ip_str; + get_gc_ip_ntoa(ip_port, &ip_str); + + assert(ip_str.length <= IP_NTOA_LEN); + memcpy(ip_addr, ip_str.buf, ip_str.length); + + return 0; +} + unsigned int gc_get_peer_connection_status(const GC_Chat *chat, uint32_t peer_id) { const int peer_number = get_peer_number_of_peer_id(chat, peer_id); diff --git a/toxcore/group_chats.h b/toxcore/group_chats.h index 066b717c..920e1a67 100644 --- a/toxcore/group_chats.h +++ b/toxcore/group_chats.h @@ -390,6 +390,29 @@ int gc_get_peer_nick_size(const GC_Chat *chat, uint32_t peer_id); non_null(1) nullable(3) int gc_get_peer_public_key_by_peer_id(const GC_Chat *chat, uint32_t peer_id, uint8_t *public_key); +/** @brief Returns the length of the IP address for the peer designated by `peer_id`. + * Returns -1 if peer_id is invalid. + */ +non_null() +int gc_get_peer_ip_address_size(const GC_Chat *chat, uint32_t peer_id); + +/** @brief Copies peer_id's IP address to `ip_addr`. + * + * If the peer is forcing TCP connections this will be a placeholder value indicating + * that their real IP address is unknown to us. + * + * If `peer_id` designates ourself, it will write either our own IP address or a + * placeholder value, depending on whether or not we're forcing TCP connections. + * + * `ip_addr` should have room for at least IP_NTOA_LEN bytes. + * + * Returns 0 on success. + * Returns -1 if peer_id is invalid or doesn't correspond to a valid peer connection. + * Returns -2 if `ip_addr` is null. + */ +non_null(1) nullable(3) +int gc_get_peer_ip_address(const GC_Chat *chat, uint32_t peer_id, uint8_t *ip_addr); + /** @brief Gets the connection status for peer associated with `peer_id`. * * If `peer_id` designates ourself, the return value indicates whether we're capable diff --git a/toxcore/network.c b/toxcore/network.c index 6228317a..d8cabc71 100644 --- a/toxcore/network.c +++ b/toxcore/network.c @@ -1514,30 +1514,29 @@ void ipport_copy(IP_Port *target, const IP_Port *source) *target = *source; } -/** @brief Converts IP into a string. - * - * Writes error message into the buffer on error. - * - * @param ip_str contains a buffer of the required size. - * - * @return Pointer to the buffer inside `ip_str` containing the IP string. - */ const char *net_ip_ntoa(const IP *ip, Ip_Ntoa *ip_str) { assert(ip_str != nullptr); + ip_str->ip_is_valid = false; + if (ip == nullptr) { snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid: NULL)"); + ip_str->length = (uint16_t)strlen(ip_str->buf); return ip_str->buf; } if (!ip_parse_addr(ip, ip_str->buf, sizeof(ip_str->buf))) { snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid, family %u)", ip->family.value); + ip_str->length = (uint16_t)strlen(ip_str->buf); return ip_str->buf; } /* brute force protection against lacking termination */ ip_str->buf[sizeof(ip_str->buf) - 1] = '\0'; + ip_str->length = (uint16_t)strlen(ip_str->buf); + ip_str->ip_is_valid = true; + return ip_str->buf; } diff --git a/toxcore/network.h b/toxcore/network.h index 2f518c1d..2a6a0833 100644 --- a/toxcore/network.h +++ b/toxcore/network.h @@ -343,11 +343,14 @@ bool ipv6_ipv4_in_v6(const IP6 *a); /** this would be TOX_INET6_ADDRSTRLEN, but it might be too short for the error message */ #define IP_NTOA_LEN 96 // TODO(irungentoo): magic number. Why not INET6_ADDRSTRLEN ? +/** Contains a null terminated string of an IP address. */ typedef struct Ip_Ntoa { - char buf[IP_NTOA_LEN]; + char buf[IP_NTOA_LEN]; // a string formatted IP address or an error message. + uint16_t length; // the length of the string (not including the null byte). + bool ip_is_valid; // if this is false `buf` will contain an error message. } Ip_Ntoa; -/** @brief Converts IP into a string. +/** @brief Converts IP into a null terminated string. * * Writes error message into the buffer on error. * diff --git a/toxcore/tox.c b/toxcore/tox.c index 05d94013..f802ce16 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c @@ -43,6 +43,8 @@ static_assert(FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE, "FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE"); static_assert(TOX_DHT_NODE_IP_STRING_SIZE == IP_NTOA_LEN, "TOX_DHT_NODE_IP_STRING_SIZE is assumed to be equal to IP_NTOA_LEN"); +static_assert(TOX_GROUP_PEER_IP_STRING_MAX_LENGTH == IP_NTOA_LEN, + "TOX_GROUP_PEER_IP_STRING_MAX_LENGTH is assumed to be equal to IP_NTOA_LEN"); static_assert(TOX_DHT_NODE_PUBLIC_KEY_SIZE == CRYPTO_PUBLIC_KEY_SIZE, "TOX_DHT_NODE_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE"); static_assert(TOX_FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE, diff --git a/toxcore/tox_api.c b/toxcore/tox_api.c index 09f91b28..9b3a888d 100644 --- a/toxcore/tox_api.c +++ b/toxcore/tox_api.c @@ -124,6 +124,10 @@ uint32_t tox_group_peer_public_key_size(void) { return TOX_GROUP_PEER_PUBLIC_KEY_SIZE; } +uint32_t tox_group_peer_ip_string_max_length(void) +{ + return TOX_GROUP_PEER_IP_STRING_MAX_LENGTH; +} uint32_t tox_dht_node_ip_string_size(void) { return TOX_DHT_NODE_IP_STRING_SIZE; diff --git a/toxcore/tox_private.c b/toxcore/tox_private.c index b42619f7..cab991f2 100644 --- a/toxcore/tox_private.c +++ b/toxcore/tox_private.c @@ -11,6 +11,7 @@ #include #include "ccompat.h" +#include "group_chats.h" #include "mem.h" #include "network.h" #include "tox_struct.h" @@ -166,3 +167,57 @@ uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *tox){ return num_cap; } +#ifndef VANILLA_NACL +size_t tox_group_peer_get_ip_address_size(const Tox *tox, uint32_t group_number, uint32_t peer_id, + Tox_Err_Group_Peer_Query *error) +{ + assert(tox != nullptr); + + tox_lock(tox); + const GC_Chat *chat = gc_get_group(tox->m->group_handler, group_number); + + if (chat == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND); + tox_unlock(tox); + return -1; + } + + const int ret = gc_get_peer_ip_address_size(chat, peer_id); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND); + return -1; + } else { + SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_OK); + return ret; + } +} + +bool tox_group_peer_get_ip_address(const Tox *tox, uint32_t group_number, uint32_t peer_id, uint8_t *ip_addr, + Tox_Err_Group_Peer_Query *error) +{ + assert(tox != nullptr); + + tox_lock(tox); + const GC_Chat *chat = gc_get_group(tox->m->group_handler, group_number); + + if (chat == nullptr) { + SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND); + tox_unlock(tox); + return false; + } + + const int ret = gc_get_peer_ip_address(chat, peer_id, ip_addr); + tox_unlock(tox); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_OK); + return true; +} + +#endif /* VANILLA_NACL */ diff --git a/toxcore/tox_private.h b/toxcore/tox_private.h index 15032936..541641a5 100644 --- a/toxcore/tox_private.h +++ b/toxcore/tox_private.h @@ -156,6 +156,51 @@ uint16_t tox_dht_get_num_closelist(const Tox *tox); */ uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *tox); + +/******************************************************************************* + * + * :: DHT groupchat queries. + * + ******************************************************************************/ + +/** + * Maximum size of a peer IP address string. + */ +#define TOX_GROUP_PEER_IP_STRING_MAX_LENGTH 96 + +uint32_t tox_group_peer_ip_string_max_length(void); + +/** + * Return the length of the peer's IP address in string form. If the group number or ID + * is invalid, the return value is unspecified. + * + * @param group_number The group number of the group we wish to query. + * @param peer_id The ID of the peer whose IP address length we want to retrieve. + */ +size_t tox_group_peer_get_ip_address_size(const Tox *tox, uint32_t group_number, uint32_t peer_id, + Tox_Err_Group_Peer_Query *error); +/** + * Write the IP address associated with the designated peer_id for the designated group number + * to ip_addr. + * + * If the peer is forcing TCP connections a placeholder value will be written instead, + * indicating that their real IP address is unknown to us. + * + * If `peer_id` designates ourself, it will write either our own IP address or a placeholder value, + * depending on whether or not we're forcing TCP connections. + * + * Call tox_group_peer_get_ip_address_size to determine the allocation size for the `ip_addr` parameter. + * + * @param group_number The group number of the group we wish to query. + * @param peer_id The ID of the peer whose public key we wish to retrieve. + * @param ip_addr A valid memory region large enough to store the IP address string. + * If this parameter is NULL, this function call has no effect. + * + * @return true on success. + */ +bool tox_group_peer_get_ip_address(const Tox *tox, uint32_t group_number, uint32_t peer_id, uint8_t *ip_addr, + Tox_Err_Group_Peer_Query *error); + #ifdef __cplusplus } #endif