toxcore/toxcore/TCP_connection.c

1521 lines
41 KiB
C

/*
* Handles TCP relay connections between two Tox clients.
*/
/*
* Copyright © 2016-2017 The TokTok team.
* Copyright © 2015 Tox project.
*
* This file is part of Tox, the free peer to peer instant messenger.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "TCP_connection.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "mono_time.h"
#include "util.h"
struct TCP_Connections {
Mono_Time *mono_time;
DHT *dht;
uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE];
uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE];
TCP_Connection_to *connections;
uint32_t connections_length; /* Length of connections array. */
TCP_con *tcp_connections;
uint32_t tcp_connections_length; /* Length of tcp_connections array. */
tcp_data_cb *tcp_data_callback;
void *tcp_data_callback_object;
tcp_oob_cb *tcp_oob_callback;
void *tcp_oob_callback_object;
tcp_onion_cb *tcp_onion_callback;
void *tcp_onion_callback_object;
TCP_Proxy_Info proxy_info;
bool onion_status;
uint16_t onion_num_conns;
};
const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c)
{
return tcp_c->self_public_key;
}
/* Set the size of the array to num.
*
* return -1 if realloc fails.
* return 0 if it succeeds.
*/
static int realloc_TCP_Connection_to(TCP_Connection_to **array, size_t num)
{
if (!num) {
free(*array);
*array = nullptr;
return 0;
}
TCP_Connection_to *temp_pointer =
(TCP_Connection_to *)realloc(*array, num * sizeof(TCP_Connection_to));
if (!temp_pointer) {
return -1;
}
*array = temp_pointer;
return 0;
}
static int realloc_TCP_con(TCP_con **array, size_t num)
{
if (!num) {
free(*array);
*array = nullptr;
return 0;
}
TCP_con *temp_pointer = (TCP_con *)realloc(*array, num * sizeof(TCP_con));
if (!temp_pointer) {
return -1;
}
*array = temp_pointer;
return 0;
}
/* return 1 if the connections_number is not valid.
* return 0 if the connections_number is valid.
*/
static bool connections_number_not_valid(const TCP_Connections *tcp_c, int connections_number)
{
if ((unsigned int)connections_number >= tcp_c->connections_length) {
return 1;
}
if (tcp_c->connections == nullptr) {
return 1;
}
if (tcp_c->connections[connections_number].status == TCP_CONN_NONE) {
return 1;
}
return 0;
}
/* return 1 if the tcp_connections_number is not valid.
* return 0 if the tcp_connections_number is valid.
*/
static bool tcp_connections_number_not_valid(const TCP_Connections *tcp_c, int tcp_connections_number)
{
if ((unsigned int)tcp_connections_number >= tcp_c->tcp_connections_length) {
return 1;
}
if (tcp_c->tcp_connections == nullptr) {
return 1;
}
if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_NONE) {
return 1;
}
return 0;
}
/* Create a new empty connection.
*
* return -1 on failure.
* return connections_number on success.
*/
static int create_connection(TCP_Connections *tcp_c)
{
uint32_t i;
for (i = 0; i < tcp_c->connections_length; ++i) {
if (tcp_c->connections[i].status == TCP_CONN_NONE) {
return i;
}
}
int id = -1;
if (realloc_TCP_Connection_to(&tcp_c->connections, tcp_c->connections_length + 1) == 0) {
id = tcp_c->connections_length;
++tcp_c->connections_length;
memset(&tcp_c->connections[id], 0, sizeof(TCP_Connection_to));
}
return id;
}
/* Create a new empty tcp connection.
*
* return -1 on failure.
* return tcp_connections_number on success.
*/
static int create_tcp_connection(TCP_Connections *tcp_c)
{
uint32_t i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
if (tcp_c->tcp_connections[i].status == TCP_CONN_NONE) {
return i;
}
}
int id = -1;
if (realloc_TCP_con(&tcp_c->tcp_connections, tcp_c->tcp_connections_length + 1) == 0) {
id = tcp_c->tcp_connections_length;
++tcp_c->tcp_connections_length;
memset(&tcp_c->tcp_connections[id], 0, sizeof(TCP_con));
}
return id;
}
/* Wipe a connection.
*
* return -1 on failure.
* return 0 on success.
*/
static int wipe_connection(TCP_Connections *tcp_c, int connections_number)
{
if (connections_number_not_valid(tcp_c, connections_number)) {
return -1;
}
uint32_t i;
memset(&tcp_c->connections[connections_number], 0, sizeof(TCP_Connection_to));
for (i = tcp_c->connections_length; i != 0; --i) {
if (tcp_c->connections[i - 1].status != TCP_CONN_NONE) {
break;
}
}
if (tcp_c->connections_length != i) {
tcp_c->connections_length = i;
realloc_TCP_Connection_to(&tcp_c->connections, tcp_c->connections_length);
}
return 0;
}
/* Wipe a connection.
*
* return -1 on failure.
* return 0 on success.
*/
static int wipe_tcp_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
if (tcp_connections_number_not_valid(tcp_c, tcp_connections_number)) {
return -1;
}
uint32_t i;
memset(&tcp_c->tcp_connections[tcp_connections_number], 0, sizeof(TCP_con));
for (i = tcp_c->tcp_connections_length; i != 0; --i) {
if (tcp_c->tcp_connections[i - 1].status != TCP_CONN_NONE) {
break;
}
}
if (tcp_c->tcp_connections_length != i) {
tcp_c->tcp_connections_length = i;
realloc_TCP_con(&tcp_c->tcp_connections, tcp_c->tcp_connections_length);
}
return 0;
}
static TCP_Connection_to *get_connection(const TCP_Connections *tcp_c, int connections_number)
{
if (connections_number_not_valid(tcp_c, connections_number)) {
return nullptr;
}
return &tcp_c->connections[connections_number];
}
static TCP_con *get_tcp_connection(const TCP_Connections *tcp_c, int tcp_connections_number)
{
if (tcp_connections_number_not_valid(tcp_c, tcp_connections_number)) {
return nullptr;
}
return &tcp_c->tcp_connections[tcp_connections_number];
}
/* Send a packet to the TCP connection.
*
* return -1 on failure.
* return 0 on success.
*/
int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return -1;
}
// TODO(irungentoo): detect and kill bad relays.
// TODO(irungentoo): thread safety?
unsigned int i;
int ret = -1;
bool limit_reached = 0;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
uint32_t tcp_con_num = con_to->connections[i].tcp_connection;
uint8_t status = con_to->connections[i].status;
uint8_t connection_id = con_to->connections[i].connection_id;
if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_ONLINE) {
tcp_con_num -= 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num);
if (!tcp_con) {
continue;
}
ret = send_data(tcp_con->connection, connection_id, packet, length);
if (ret == 0) {
limit_reached = 1;
}
if (ret == 1) {
break;
}
}
}
if (ret == 1) {
return 0;
}
if (!limit_reached) {
ret = 0;
/* Send oob packets to all relays tied to the connection. */
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
uint32_t tcp_con_num = con_to->connections[i].tcp_connection;
uint8_t status = con_to->connections[i].status;
if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_REGISTERED) {
tcp_con_num -= 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num);
if (!tcp_con) {
continue;
}
if (send_oob_packet(tcp_con->connection, con_to->public_key, packet, length) == 1) {
ret += 1;
}
}
}
if (ret >= 1) {
return 0;
}
return -1;
}
return -1;
}
/* Return a random TCP connection number for use in send_tcp_onion_request.
*
* TODO(irungentoo): This number is just the index of an array that the elements
* can change without warning.
*
* return TCP connection number on success.
* return -1 on failure.
*/
int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c)
{
const uint32_t r = random_u32();
for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) {
uint32_t index = ((i + r) % tcp_c->tcp_connections_length);
if (tcp_c->tcp_connections[index].onion && tcp_c->tcp_connections[index].status == TCP_CONN_CONNECTED) {
return index;
}
}
return -1;
}
/* Send an onion packet via the TCP relay corresponding to tcp_connections_number.
*
* return 0 on success.
* return -1 on failure.
*/
int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data,
uint16_t length)
{
if (tcp_connections_number >= tcp_c->tcp_connections_length) {
return -1;
}
if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_CONNECTED) {
int ret = send_onion_request(tcp_c->tcp_connections[tcp_connections_number].connection, data, length);
if (ret == 1) {
return 0;
}
}
return -1;
}
/* Send an oob packet via the TCP relay corresponding to tcp_connections_number.
*
* return 0 on success.
* return -1 on failure.
*/
int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key,
const uint8_t *packet, uint16_t length)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (tcp_con->status != TCP_CONN_CONNECTED) {
return -1;
}
int ret = send_oob_packet(tcp_con->connection, public_key, packet, length);
if (ret == 1) {
return 0;
}
return -1;
}
/* Set the callback for TCP data packets.
*/
void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_data_cb *tcp_data_callback, void *object)
{
tcp_c->tcp_data_callback = tcp_data_callback;
tcp_c->tcp_data_callback_object = object;
}
/* Set the callback for TCP onion packets.
*/
void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_oob_cb *tcp_oob_callback, void *object)
{
tcp_c->tcp_oob_callback = tcp_oob_callback;
tcp_c->tcp_oob_callback_object = object;
}
/* Set the callback for TCP oob data packets.
*/
void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_onion_cb *tcp_onion_callback, void *object)
{
tcp_c->tcp_onion_callback = tcp_onion_callback;
tcp_c->tcp_onion_callback_object = object;
}
/* Find the TCP connection with public_key.
*
* return connections_number on success.
* return -1 on failure.
*/
static int find_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key)
{
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
if (public_key_cmp(con_to->public_key, public_key) == 0) {
return i;
}
}
}
return -1;
}
/* Find the TCP connection to a relay with relay_pk.
*
* return connections_number on success.
* return -1 on failure.
*/
static int find_tcp_connection_relay(TCP_Connections *tcp_c, const uint8_t *relay_pk)
{
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_SLEEPING) {
if (public_key_cmp(tcp_con->relay_pk, relay_pk) == 0) {
return i;
}
} else {
if (public_key_cmp(tcp_con_public_key(tcp_con->connection), relay_pk) == 0) {
return i;
}
}
}
}
return -1;
}
/* Create a new TCP connection to public_key.
*
* public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections().
*
* id is the id in the callbacks for that connection.
*
* return connections_number on success.
* return -1 on failure.
*/
int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id)
{
if (find_tcp_connection_to(tcp_c, public_key) != -1) {
return -1;
}
int connections_number = create_connection(tcp_c);
if (connections_number == -1) {
return -1;
}
TCP_Connection_to *con_to = &tcp_c->connections[connections_number];
con_to->status = TCP_CONN_VALID;
memcpy(con_to->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE);
con_to->id = id;
return connections_number;
}
/* return 0 on success.
* return -1 on failure.
*/
int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return -1;
}
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
continue;
}
if (tcp_con->status == TCP_CONN_CONNECTED) {
send_disconnect_request(tcp_con->connection, con_to->connections[i].connection_id);
}
if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) {
--tcp_con->lock_count;
if (con_to->status == TCP_CONN_SLEEPING) {
--tcp_con->sleep_count;
}
}
}
}
return wipe_connection(tcp_c, connections_number);
}
/* Set connection status.
*
* status of 1 means we are using the connection.
* status of 0 means we are not using it.
*
* Unused tcp connections will be disconnected from but kept in case they are needed.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, bool status)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return -1;
}
if (status) {
/* Connection is unsleeping. */
if (con_to->status != TCP_CONN_SLEEPING) {
return -1;
}
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
continue;
}
if (tcp_con->status == TCP_CONN_SLEEPING) {
tcp_con->unsleep = 1;
}
}
}
con_to->status = TCP_CONN_VALID;
return 0;
}
/* Connection is going to sleep. */
if (con_to->status != TCP_CONN_VALID) {
return -1;
}
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
continue;
}
if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) {
++tcp_con->sleep_count;
}
}
}
con_to->status = TCP_CONN_SLEEPING;
return 0;
}
static bool tcp_connection_in_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number)
{
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) {
return 1;
}
}
return 0;
}
/* return index on success.
* return -1 on failure.
*/
static int add_tcp_connection_to_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number)
{
unsigned int i;
if (tcp_connection_in_conn(con_to, tcp_connections_number)) {
return -1;
}
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == 0) {
con_to->connections[i].tcp_connection = tcp_connections_number + 1;
con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE;
con_to->connections[i].connection_id = 0;
return i;
}
}
return -1;
}
/* return index on success.
* return -1 on failure.
*/
static int rm_tcp_connection_from_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number)
{
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) {
con_to->connections[i].tcp_connection = 0;
con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE;
con_to->connections[i].connection_id = 0;
return i;
}
}
return -1;
}
/* return number of online connections on success.
* return -1 on failure.
*/
static unsigned int online_tcp_connection_from_conn(TCP_Connection_to *con_to)
{
unsigned int i, count = 0;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) {
++count;
}
}
}
return count;
}
/* return index on success.
* return -1 on failure.
*/
static int set_tcp_connection_status(TCP_Connection_to *con_to, unsigned int tcp_connections_number,
unsigned int status, uint8_t connection_id)
{
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) {
if (con_to->connections[i].status == status) {
return -1;
}
con_to->connections[i].status = status;
con_to->connections[i].connection_id = connection_id;
return i;
}
}
return -1;
}
/* Kill a TCP relay connection.
*
* return 0 on success.
* return -1 on failure.
*/
static int kill_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
rm_tcp_connection_from_conn(con_to, tcp_connections_number);
}
}
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
}
kill_TCP_connection(tcp_con->connection);
return wipe_tcp_connection(tcp_c, tcp_connections_number);
}
static int reconnect_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (tcp_con->status == TCP_CONN_SLEEPING) {
return -1;
}
IP_Port ip_port = tcp_con_ip_port(tcp_con->connection);
uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE];
memcpy(relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE);
kill_TCP_connection(tcp_con->connection);
tcp_con->connection = new_TCP_connection(tcp_c->mono_time, ip_port, relay_pk, tcp_c->self_public_key,
tcp_c->self_secret_key, &tcp_c->proxy_info);
if (!tcp_con->connection) {
kill_tcp_relay_connection(tcp_c, tcp_connections_number);
return -1;
}
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0);
}
}
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
tcp_con->onion = 0;
}
tcp_con->lock_count = 0;
tcp_con->sleep_count = 0;
tcp_con->connected_time = 0;
tcp_con->status = TCP_CONN_VALID;
tcp_con->unsleep = 0;
return 0;
}
static int sleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (tcp_con->status != TCP_CONN_CONNECTED) {
return -1;
}
if (tcp_con->lock_count != tcp_con->sleep_count) {
return -1;
}
tcp_con->ip_port = tcp_con_ip_port(tcp_con->connection);
memcpy(tcp_con->relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE);
kill_TCP_connection(tcp_con->connection);
tcp_con->connection = nullptr;
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0);
}
}
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
tcp_con->onion = 0;
}
tcp_con->lock_count = 0;
tcp_con->sleep_count = 0;
tcp_con->connected_time = 0;
tcp_con->status = TCP_CONN_SLEEPING;
tcp_con->unsleep = 0;
return 0;
}
static int unsleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (tcp_con->status != TCP_CONN_SLEEPING) {
return -1;
}
tcp_con->connection = new_TCP_connection(tcp_c->mono_time, tcp_con->ip_port, tcp_con->relay_pk, tcp_c->self_public_key,
tcp_c->self_secret_key, &tcp_c->proxy_info);
if (!tcp_con->connection) {
kill_tcp_relay_connection(tcp_c, tcp_connections_number);
return -1;
}
tcp_con->lock_count = 0;
tcp_con->sleep_count = 0;
tcp_con->connected_time = 0;
tcp_con->status = TCP_CONN_VALID;
tcp_con->unsleep = 0;
return 0;
}
/* Send a TCP routing request.
*
* return 0 on success.
* return -1 on failure.
*/
static int send_tcp_relay_routing_request(TCP_Connections *tcp_c, int tcp_connections_number, uint8_t *public_key)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (tcp_con->status == TCP_CONN_SLEEPING) {
return -1;
}
if (send_routing_request(tcp_con->connection, public_key) != 1) {
return -1;
}
return 0;
}
static int tcp_response_callback(void *object, uint8_t connection_id, const uint8_t *public_key)
{
TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object;
TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con);
unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con);
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
int connections_number = find_tcp_connection_to(tcp_c, public_key);
if (connections_number == -1) {
return -1;
}
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (con_to == nullptr) {
return -1;
}
if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) {
return -1;
}
set_tcp_connection_number(tcp_con->connection, connection_id, connections_number);
return 0;
}
static int tcp_status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status)
{
TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object;
TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con);
unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con);
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
TCP_Connection_to *con_to = get_connection(tcp_c, number);
if (!con_to || !tcp_con) {
return -1;
}
if (status == 1) {
if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) {
return -1;
}
--tcp_con->lock_count;
if (con_to->status == TCP_CONN_SLEEPING) {
--tcp_con->sleep_count;
}
} else if (status == 2) {
if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_ONLINE, connection_id) == -1) {
return -1;
}
++tcp_con->lock_count;
if (con_to->status == TCP_CONN_SLEEPING) {
++tcp_con->sleep_count;
}
}
return 0;
}
static int tcp_conn_data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data,
uint16_t length, void *userdata)
{
if (length == 0) {
return -1;
}
TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object;
TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con);
unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con);
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
TCP_Connection_to *con_to = get_connection(tcp_c, number);
if (!con_to) {
return -1;
}
if (tcp_c->tcp_data_callback) {
tcp_c->tcp_data_callback(tcp_c->tcp_data_callback_object, con_to->id, data, length, userdata);
}
return 0;
}
static int tcp_conn_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length,
void *userdata)
{
if (length == 0) {
return -1;
}
TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object;
TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con);
unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con);
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
/* TODO(irungentoo): optimize */
int connections_number = find_tcp_connection_to(tcp_c, public_key);
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (con_to && tcp_connection_in_conn(con_to, tcp_connections_number)) {
return tcp_conn_data_callback(object, connections_number, 0, data, length, userdata);
}
if (tcp_c->tcp_oob_callback) {
tcp_c->tcp_oob_callback(tcp_c->tcp_oob_callback_object, public_key, tcp_connections_number, data, length, userdata);
}
return 0;
}
static int tcp_onion_callback(void *object, const uint8_t *data, uint16_t length, void *userdata)
{
TCP_Connections *tcp_c = (TCP_Connections *)object;
if (tcp_c->tcp_onion_callback) {
tcp_c->tcp_onion_callback(tcp_c->tcp_onion_callback_object, data, length, userdata);
}
return 0;
}
/* Set callbacks for the TCP relay connection.
*
* return 0 on success.
* return -1 on failure.
*/
static int tcp_relay_set_callbacks(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
TCP_Client_Connection *con = tcp_con->connection;
tcp_con_set_custom_object(con, tcp_c);
tcp_con_set_custom_uint(con, tcp_connections_number);
onion_response_handler(con, &tcp_onion_callback, tcp_c);
routing_response_handler(con, &tcp_response_callback, con);
routing_status_handler(con, &tcp_status_callback, con);
routing_data_handler(con, &tcp_conn_data_callback, con);
oob_data_handler(con, &tcp_conn_oob_callback, con);
return 0;
}
static int tcp_relay_on_online(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
unsigned int i, sent = 0;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
if (tcp_connection_in_conn(con_to, tcp_connections_number)) {
if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) {
++sent;
}
}
}
}
tcp_relay_set_callbacks(tcp_c, tcp_connections_number);
tcp_con->status = TCP_CONN_CONNECTED;
/* If this connection isn't used by any connection, we don't need to wait for them to come online. */
if (sent) {
tcp_con->connected_time = mono_time_get(tcp_c->mono_time);
} else {
tcp_con->connected_time = 0;
}
if (tcp_c->onion_status && tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) {
tcp_con->onion = 1;
++tcp_c->onion_num_conns;
}
return 0;
}
static int add_tcp_relay_instance(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk)
{
if (net_family_is_tcp_ipv4(ip_port.ip.family)) {
ip_port.ip.family = net_family_ipv4;
} else if (net_family_is_tcp_ipv6(ip_port.ip.family)) {
ip_port.ip.family = net_family_ipv6;
}
if (!net_family_is_ipv4(ip_port.ip.family) && !net_family_is_ipv6(ip_port.ip.family)) {
return -1;
}
int tcp_connections_number = create_tcp_connection(tcp_c);
if (tcp_connections_number == -1) {
return -1;
}
TCP_con *tcp_con = &tcp_c->tcp_connections[tcp_connections_number];
tcp_con->connection = new_TCP_connection(tcp_c->mono_time, ip_port, relay_pk, tcp_c->self_public_key,
tcp_c->self_secret_key, &tcp_c->proxy_info);
if (!tcp_con->connection) {
return -1;
}
tcp_con->status = TCP_CONN_VALID;
return tcp_connections_number;
}
/* Add a TCP relay to the TCP_Connections instance.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk)
{
int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk);
if (tcp_connections_number != -1) {
return -1;
}
if (add_tcp_relay_instance(tcp_c, ip_port, relay_pk) == -1) {
return -1;
}
return 0;
}
/* Add a TCP relay tied to a connection.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number, unsigned int tcp_connections_number)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return -1;
}
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (con_to->status != TCP_CONN_SLEEPING && tcp_con->status == TCP_CONN_SLEEPING) {
tcp_con->unsleep = 1;
}
if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) {
return -1;
}
if (tcp_con->status == TCP_CONN_CONNECTED) {
if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) {
tcp_con->connected_time = mono_time_get(tcp_c->mono_time);
}
}
return 0;
}
/* Add a TCP relay tied to a connection.
*
* This should be called with the same relay by two peers who want to create a TCP connection with each other.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return -1;
}
int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk);
if (tcp_connections_number != -1) {
return add_tcp_number_relay_connection(tcp_c, connections_number, tcp_connections_number);
}
if (online_tcp_connection_from_conn(con_to) >= RECOMMENDED_FRIEND_TCP_CONNECTIONS) {
return -1;
}
tcp_connections_number = add_tcp_relay_instance(tcp_c, ip_port, relay_pk);
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con) {
return -1;
}
if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) {
return -1;
}
return 0;
}
/* return number of online tcp relays tied to the connection on success.
* return 0 on failure.
*/
unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return 0;
}
return online_tcp_connection_from_conn(con_to);
}
/* Copy a maximum of max_num TCP relays we are connected to to tcp_relays.
* NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6.
*
* return number of relays copied to tcp_relays on success.
* return 0 on failure.
*/
uint32_t tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num)
{
const uint32_t r = random_u32();
uint32_t copied = 0;
for (uint32_t i = 0; (i < tcp_c->tcp_connections_length) && (copied < max_num); ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, (i + r) % tcp_c->tcp_connections_length);
if (!tcp_con) {
continue;
}
if (tcp_con->status == TCP_CONN_CONNECTED) {
memcpy(tcp_relays[copied].public_key, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE);
tcp_relays[copied].ip_port = tcp_con_ip_port(tcp_con->connection);
Family *const family = &tcp_relays[copied].ip_port.ip.family;
if (net_family_is_ipv4(*family)) {
*family = net_family_tcp_ipv4;
} else if (net_family_is_ipv6(*family)) {
*family = net_family_tcp_ipv6;
}
++copied;
}
}
return copied;
}
/* Set if we want TCP_connection to allocate some connection for onion use.
*
* If status is 1, allocate some connections. if status is 0, don't.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_onion_status(TCP_Connections *tcp_c, bool status)
{
if (tcp_c->onion_status == status) {
return -1;
}
if (status) {
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion) {
++tcp_c->onion_num_conns;
tcp_con->onion = 1;
}
}
if (tcp_c->onion_num_conns >= NUM_ONION_TCP_CONNECTIONS) {
break;
}
}
if (tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) {
unsigned int wakeup = NUM_ONION_TCP_CONNECTIONS - tcp_c->onion_num_conns;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_SLEEPING) {
tcp_con->unsleep = 1;
}
}
if (!wakeup) {
break;
}
}
}
tcp_c->onion_status = 1;
} else {
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
tcp_con->onion = 0;
}
}
}
tcp_c->onion_status = 0;
}
return 0;
}
/* Returns a new TCP_Connections object associated with the secret_key.
*
* In order for others to connect to this instance new_tcp_connection_to() must be called with the
* public_key associated with secret_key.
*
* Returns NULL on failure.
*/
TCP_Connections *new_tcp_connections(Mono_Time *mono_time, const uint8_t *secret_key, TCP_Proxy_Info *proxy_info)
{
if (secret_key == nullptr) {
return nullptr;
}
TCP_Connections *temp = (TCP_Connections *)calloc(1, sizeof(TCP_Connections));
if (temp == nullptr) {
return nullptr;
}
temp->mono_time = mono_time;
memcpy(temp->self_secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE);
crypto_derive_public_key(temp->self_public_key, temp->self_secret_key);
temp->proxy_info = *proxy_info;
return temp;
}
static void do_tcp_conns(TCP_Connections *tcp_c, void *userdata)
{
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status != TCP_CONN_SLEEPING) {
do_TCP_connection(tcp_c->mono_time, tcp_con->connection, userdata);
/* callbacks can change TCP connection address. */
tcp_con = get_tcp_connection(tcp_c, i);
// Make sure the TCP connection wasn't dropped in any of the callbacks.
assert(tcp_con != nullptr);
if (tcp_con_status(tcp_con->connection) == TCP_CLIENT_DISCONNECTED) {
if (tcp_con->status == TCP_CONN_CONNECTED) {
reconnect_tcp_relay_connection(tcp_c, i);
} else {
kill_tcp_relay_connection(tcp_c, i);
}
continue;
}
if (tcp_con->status == TCP_CONN_VALID && tcp_con_status(tcp_con->connection) == TCP_CLIENT_CONFIRMED) {
tcp_relay_on_online(tcp_c, i);
}
if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion && tcp_con->lock_count
&& tcp_con->lock_count == tcp_con->sleep_count
&& mono_time_is_timeout(tcp_c->mono_time, tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) {
sleep_tcp_relay_connection(tcp_c, i);
}
}
if (tcp_con->status == TCP_CONN_SLEEPING && tcp_con->unsleep) {
unsleep_tcp_relay_connection(tcp_c, i);
}
}
}
}
static void kill_nonused_tcp(TCP_Connections *tcp_c)
{
if (tcp_c->tcp_connections_length == 0) {
return;
}
unsigned int i;
unsigned int num_online = 0;
unsigned int num_kill = 0;
VLA(unsigned int, to_kill, tcp_c->tcp_connections_length);
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_CONNECTED) {
if (!tcp_con->onion && !tcp_con->lock_count
&& mono_time_is_timeout(tcp_c->mono_time, tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) {
to_kill[num_kill] = i;
++num_kill;
}
++num_online;
}
}
}
if (num_online <= RECOMMENDED_FRIEND_TCP_CONNECTIONS) {
return;
}
unsigned int n = num_online - RECOMMENDED_FRIEND_TCP_CONNECTIONS;
if (n < num_kill) {
num_kill = n;
}
for (i = 0; i < num_kill; ++i) {
kill_tcp_relay_connection(tcp_c, to_kill[i]);
}
}
void do_tcp_connections(TCP_Connections *tcp_c, void *userdata)
{
do_tcp_conns(tcp_c, userdata);
kill_nonused_tcp(tcp_c);
}
void kill_tcp_connections(TCP_Connections *tcp_c)
{
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
kill_TCP_connection(tcp_c->tcp_connections[i].connection);
}
free(tcp_c->tcp_connections);
free(tcp_c->connections);
free(tcp_c);
}