diff --git a/Makefile.am b/Makefile.am index add903b1623d0ae4c63482e7f894806bd3ef816e..b0b8e632cd5f889635e605480189e6707da856bc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,6 +8,7 @@ src_coincerd_SOURCES = \ src/crypto.c src/crypto.h \ src/daemon_events.c src/daemon_events.h \ src/daemon_messages.c src/daemon_messages.h \ + src/daemon_messages_processor.c src/daemon_messages_processor.h \ src/global_state.c src/global_state.h \ src/hosts.c src/hosts.h \ src/json_parser.c src/json_parser.h \ @@ -15,7 +16,9 @@ src_coincerd_SOURCES = \ src/log.c src/log.h \ src/neighbours.c src/neighbours.h \ src/p2p.c src/p2p.h \ - src/paths.c src/paths.h + src/paths.c src/paths.h \ + src/peers.c src/peers.h \ + src/routing.c src/routing.h src_coincerd_CFLAGS = $(AM_CFLAGS) $(JANSSON_CFLAGS) $(LIBEVENT_CFLAGS) $(LIBSODIUM_CFLAGS) src_coincerd_LDADD = $(AM_LDADD) $(JANSSON_LIBS) $(LIBEVENT_LIBS) $(LIBSODIUM_LIBS) diff --git a/README b/README deleted file mode 100644 index e5e06d186bddd3efcdc800a2b4fb8a693b937d51..0000000000000000000000000000000000000000 --- a/README +++ /dev/null @@ -1,2 +0,0 @@ -Coincer -======= diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8897953c5e7ad5a12d90dcc9c3e1b98a015bfc0e --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Coincer + +## Terminology +- Host: A device that is possibly connected to the Coincer network +- Identifier: The public key of a cryptographic keypair +- Identity: A peer representation that we own (identifier + its corresponding +secret key) +- Message trace: Contains a peer-to-peer message's nonce, a pointer to a + neighbour from which we've received the message and the originator's + identifier. The message traces are being used for routing loop detection +- n2n: neighbour-to-neighbour +- Neighbour: A host that we are interconnected with +- Nonce: Ever-increasing integer tied to every message sent for message + repetiton or replay attack detection +- p2p: peer-to-peer +- Peer: The Coincer network participant diff --git a/src/daemon_events.c b/src/daemon_events.c index 639a08241f78563fe34465f0f8d0db1e2dac9d05..4307d5db117d21a84fd34d30dbc8dddc00e2c109 100644 --- a/src/daemon_events.c +++ b/src/daemon_events.c @@ -25,31 +25,46 @@ #include "log.h" #include "neighbours.h" #include "p2p.h" +#include "peers.h" +#include "routing.h" + +/** The time interval in seconds between loop_update_long calls. */ +#define UPDATE_TIME_LONG 60 +/** The time interval in seconds between loop_update_medium calls. */ +#define UPDATE_TIME_MEDIUM 30 +/** The time interval in seconds between loop_update_short calls. */ +#define UPDATE_TIME_SHORT 10 + +static void loop_update_long(evutil_socket_t fd __attribute__((unused)), + short events __attribute__((unused)), + void *ctx); +static void loop_update_medium(evutil_socket_t fd __attribute__((unused)), + short events __attribute__((unused)), + void *ctx); +static void loop_update_short(evutil_socket_t fd __attribute__((unused)), + short events __attribute__((unused)), + void *ctx); + +static void remove_stale_records(global_state_t *global_state); static void signal_cb(evutil_socket_t signal __attribute__((unused)), short events __attribute__((unused)), void *ctx); /** - * Actively check the number of neighbours and ask for more if needed. + * Check the number of neighbours and ask for more if needed. * - * @param fd File descriptor. - * @param events Event flags. - * @param ctx Global state. + * @param global_state The global state. */ -static void conns_cb(int fd __attribute__((unused)), - short events __attribute__((unused)), - void *ctx) +static void connections_maintain(global_state_t *global_state) { - global_state_t *global_state; - int needed_conns; - - global_state = (global_state_t *) ctx; + int needed_conns; needed_conns = MIN_NEIGHBOURS - linkedlist_size(&global_state->neighbours); if (needed_conns > 0) { - log_debug("conns_cb - we need %d more neighbours"); + log_debug("connections_maintain - need %d more neighbours", + needed_conns); /* ask twice more hosts than we need; it's preferable * to have more neighbours than minimum */ add_more_connections(global_state, 2 * needed_conns); @@ -96,25 +111,180 @@ int daemon_events_setup(global_state_t *global_state) return 1; } - /* check the number of neighbours every 10 seconds */ - interval.tv_sec = 10; + /* register the short time-interval loop updates */ + interval.tv_sec = UPDATE_TIME_SHORT; + interval.tv_usec = 0; + + event = event_new(global_state->event_loop, + -1, + EV_PERSIST, + loop_update_short, + global_state); + if (!event || + event_add(event, &interval) < 0 || + !linkedlist_append(events, event)) { + log_error("Creating or adding short loop update callback"); + return 1; + } + + /* register the medium time-interval loop updates */ + interval.tv_sec = UPDATE_TIME_MEDIUM; + interval.tv_usec = 0; + + event = event_new(global_state->event_loop, + -1, + EV_PERSIST, + loop_update_medium, + global_state); + + if (!event || + event_add(event, &interval) < 0 || + !linkedlist_append(events, event)) { + log_error("Creating or adding medium loop update callback"); + return 1; + } + + /* register the long time-interval loop updates */ + interval.tv_sec = UPDATE_TIME_LONG; interval.tv_usec = 0; event = event_new(global_state->event_loop, -1, EV_PERSIST, - conns_cb, + loop_update_long, global_state); + if (!event || event_add(event, &interval) < 0 || !linkedlist_append(events, event)) { - log_error("Creating or adding Connections event"); + log_error("Creating or adding long loop update callback"); return 1; } return 0; } +/** + * The long time-triggered loop update. + * + * @param fd File descriptor. + * @param events Event flags. + * @param ctx Global state. + */ +static void loop_update_long(evutil_socket_t fd __attribute__((unused)), + short events __attribute__((unused)), + void *ctx) +{ + global_state_t *global_state = (global_state_t *) ctx; + + remove_stale_records(global_state); +} + +/** + * The medium time-triggered loop update. + * + * @param fd File descriptor. + * @param events Event flags. + * @param ctx Global state. + */ +static void loop_update_medium(evutil_socket_t fd __attribute__((unused)), + short events __attribute__((unused)), + void *ctx) +{ + global_state_t *global_state = (global_state_t *) ctx; + + if (linkedlist_size(&global_state->neighbours) >= MIN_NEIGHBOURS) { + send_p2p_route_adv(&global_state->neighbours, + global_state->true_identity); + } +} + +/** + * The short time-triggered loop update. + * + * @param fd File descriptor. + * @param events Event flags. + * @param ctx Global state. + */ +static void loop_update_short(evutil_socket_t fd __attribute__((unused)), + short events __attribute__((unused)), + void *ctx) +{ + global_state_t *global_state = (global_state_t *) ctx; + + connections_maintain(global_state); +} + +/** + * Remove old records from every global state's container. + * + * @param global_state The global state. + */ +static void remove_stale_records(global_state_t *global_state) +{ + identity_t *current_identity; + linkedlist_t *current_list; + neighbour_t *current_neighbour; + linkedlist_node_t *current_node; + peer_t *current_peer; + time_t current_time; + linkedlist_node_t *next_node; + + current_time = time(NULL); + + /* if a route is stale, delete it from the routing table */ + linkedlist_apply_if(&global_state->routing_table, + ¤t_time, + route_is_stale, + route_clear, + linkedlist_delete); + + /* remove stale nonces of known peers */ + current_list = &global_state->peers; + current_node = linkedlist_get_first(current_list); + while (current_node) { + current_peer = (peer_t *) current_node->data; + + nonces_remove_stale(¤t_peer->nonces); + + current_node = linkedlist_get_next(current_list, current_node); + } + + /* remove stale nonces of the pseudonyms of our neighbours */ + current_list = &global_state->neighbours; + current_node = linkedlist_get_first(current_list); + while (current_node) { + current_neighbour = (neighbour_t *) current_node->data; + + nonces_remove_stale(¤t_neighbour->pseudonym.nonces); + + current_node = linkedlist_get_next(current_list, current_node); + } + + /* remove stale message traces */ + linkedlist_apply_if(&global_state->message_traces, + ¤t_time, + message_trace_is_stale, + NULL, + linkedlist_delete); + + /* remove unneeded identities */ + current_list = &global_state->identities; + current_node = linkedlist_get_first(current_list); + while (current_node) { + current_identity = (identity_t *) current_node->data; + next_node = linkedlist_get_next(current_list, current_node); + + if (current_identity->flags & IDENTITY_TMP) { + send_p2p_bye(&global_state->neighbours, + current_identity); + linkedlist_delete(current_node); + } + + current_node = next_node; + } +} + /** * Callback function for a received signal. * diff --git a/src/daemon_messages_processor.c b/src/daemon_messages_processor.c new file mode 100644 index 0000000000000000000000000000000000000000..3e1168b3d2282a8b9559cad243a58f832f8a2e03 --- /dev/null +++ b/src/daemon_messages_processor.c @@ -0,0 +1,693 @@ +/* + * Coincer + * Copyright (C) 2017-2018 Coincer Developers + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "daemon_messages.h" +#include "daemon_messages_processor.h" +#include "global_state.h" +#include "hosts.h" +#include "json_parser.h" +#include "log.h" +#include "neighbours.h" +#include "routing.h" + +/** The minimum time in seconds that has to pass between the announcements of + * presence of one identity. */ +#define ADV_GAP_TIME 10 + +static enum process_message_result + process_message(const message_t *message, + const char *json_message, + neighbour_t *sender, + global_state_t *global_state); + +static int process_p2p_bye(const message_t *message, + const char *json_message, + neighbour_t *sender, + peer_t *sender_peer, + global_state_t *global_state); +static int process_p2p_hello(const message_t *message, + neighbour_t *sender, + linkedlist_t *neighbours, + unsigned short port); +static int process_p2p_peers_adv(const message_t *message, + neighbour_t *sender, + linkedlist_t *hosts); +static int process_p2p_peers_sol(neighbour_t *sender, + const linkedlist_t *hosts); +static int process_p2p_ping(neighbour_t *sender); +static int process_p2p_pong(neighbour_t *sender); +static int process_p2p_route_adv(const message_t *message, + const char *json_message, + neighbour_t *sender, + peer_t *sender_peer, + global_state_t *global_state); +static int process_p2p_route_sol(const message_t *message, + const char *json_message, + neighbour_t *sender, + global_state_t *global_state); + +/** + * Process a JSON message received from its forwarder/sender (our neighbour). + * + * @param json_message The received JSON message. + * @param sender The message sender/forwarder. + * @param global_state The global state. + * + * @return PMR_DONE The received message was successfully + * processed. + * @return PMR_ERR_INTEGRITY Someone tampered with the message. + * @return PMR_ERR_INTERNAL Internal processing error. + * @return PMR_ERR_PARSING Parsing failure. + * @return PMR_ERR_SEMANTIC Semantic error. + * @return PMR_ERR_VERSION The message is of different + * protocol version. + */ +enum process_message_result + process_encoded_message(const char *json_message, + neighbour_t *sender, + global_state_t *global_state) +{ + char *json_message_body; + message_t message; + enum process_message_result ret; + + /* decode JSON message; if parsing JSON message into message_t failed */ + if (decode_message(json_message, &message, &json_message_body)) { + log_debug("process_encoded_message - decoding a received " + "message has failed. The message:\n%s", json_message); + return PMR_ERR_PARSING; + } + + if (message.version != PROTOCOL_VERSION) { + free(json_message_body); + message_delete(&message); + return PMR_ERR_VERSION; + } + + /* integrity verification part; if the message integrity is violated */ + if (verify_signature(json_message_body, message.from, message.sig)) { + log_warn("Someone tampered with a received message"); + log_debug("The tampered message:\n%s", json_message); + free(json_message_body); + message_delete(&message); + return PMR_ERR_INTEGRITY; + } + /* integrity verification done */ + free(json_message_body); + + /* process the message */ + ret = process_message(&message, json_message, sender, global_state); + + message_delete(&message); + return ret; +} + +/** + * Process a message received from its forwarder/sender (our neighbour). + * + * @param message The received message. + * @param json_message The received message in the JSON format. + * @param sender The message sender/forwarder. + * @param global_state The global state. + * + * @return PMR_DONE The received message was successfully + * processed. + * @return PMR_ERR_INTERNAL Internal processing error. + * @return PMR_ERR_SEMANTIC Semantic error. + */ +static enum process_message_result + process_message(const message_t *message, + const char *json_message, + neighbour_t *sender, + global_state_t *global_state) +{ + int cmp_val; + linkedlist_t *hosts; + linkedlist_t *identities; + identity_t *identity; + const message_body_t *msg_body; + const linkedlist_t *msg_traces; + enum message_type msg_type; + linkedlist_t *neighbours; + uint64_t nonce_value; + linkedlist_t *peers; + int res; + linkedlist_t *routing_table; + peer_t *sender_peer; + + msg_body = &message->body; + msg_type = msg_body->type; + nonce_value = msg_body->nonce; + + hosts = &global_state->hosts; + identities = &global_state->identities; + msg_traces = &global_state->message_traces; + neighbours = &global_state->neighbours; + peers = &global_state->peers; + routing_table = &global_state->routing_table; + + /* if we haven't yet received p2p.hello from the neighbour who's + * sent/forwarded the received message */ + if (!(sender->flags & NEIGHBOUR_ACTIVE)) { + /* and the message is not p2p.hello */ + if (msg_type != P2P_HELLO) { + log_debug("process_message - received non-hello msg " + "before p2p.hello"); + return PMR_ERR_SEMANTIC; + } + res = process_p2p_hello(message, + sender, + neighbours, + global_state->port); + if (!res) { + nonce_store(&sender->pseudonym.nonces, nonce_value); + return PMR_DONE; + } + + return PMR_ERR_INTERNAL; + } + + /* if we've received a message that we've created */ + if (identity_find(identities, message->from)) { + /* and if it is a p2p message */ + if (!identifier_empty(msg_body->to)) { + routing_loop_remove(routing_table, + neighbours, + identities, + msg_body->to); + } + return PMR_DONE; + } + + /* let's get peer representations of the sender and appropriate ours */ + + cmp_val = 0; + sender_peer = NULL; + /* all bytes of 'to' are set to 0x0 => we are one of the recipients */ + if (identifier_empty(msg_body->to)) { + /* 'from' different from the neighbour's pseudonym => + * the message should be a broadcast (we will check later) */ + if (memcmp(message->from, + sender->pseudonym.identifier, + crypto_box_PUBLICKEYBYTES)) { + /* process the broadcast with the true identity */ + identity = global_state->true_identity; + /* otherwise it is a n2n message */ + } else { + identity = &sender->my_pseudonym; + sender_peer = &sender->pseudonym; + + /* for later parity check */ + cmp_val = memcmp(message->from, + identity->keypair.public_key, + crypto_box_PUBLICKEYBYTES); + } + /* the message is of type p2p */ + } else { + /* identity will be NULL if the message is not meant for us */ + identity = identity_find(identities, msg_body->to); + /* for later parity check */ + cmp_val = memcmp(message->from, + msg_body->to, + crypto_box_PUBLICKEYBYTES); + } + + /* nonce parity check; the message must have: + * sender ID > our ID => odd nonce; sender ID < our ID => even nonce */ + if ((cmp_val > 0 && !(nonce_value & 0x01)) || + (cmp_val < 0 && (nonce_value & 0x01))) { + log_debug("process_mesage - wrong nonce parity"); + return PMR_ERR_SEMANTIC; + } + + /* if the message is not n2n */ + if (!sender_peer) { + /* from what peer is this message? */ + sender_peer = peer_find(peers, message->from); + } + /* if we don't know this peer yet */ + if (!sender_peer) { + sender_peer = peer_store(peers, message->from); + /* storing the new peer has failed */ + if (!sender_peer) { + return PMR_ERR_INTERNAL; + } + } else { + if (msg_type == P2P_ROUTE_ADV) { + /* if it's an old announcement of presence, skip it */ + if (nonce_value < sender_peer->presence_nonce.value) { + return PMR_DONE; + } + } else { + /* have we already processed this message? */ + if (nonces_find(&sender_peer->nonces, nonce_value)) { + /* if broadcast or n2n msg */ + if (identifier_empty(msg_body->to) || + identity == &sender->my_pseudonym) { + return PMR_DONE; + } + /* check if there's a routing loop */ + if (routing_loop_detect(msg_traces, + sender, + nonce_value, + message->from)) { + routing_loop_remove(routing_table, + neighbours, + identities, + msg_body->to); + } + return PMR_DONE; + } + /* if the message smells like replay attack */ + if (!linkedlist_empty(&sender_peer->nonces) && + nonce_value < + (nonces_get_oldest(&sender_peer->nonces))->value) { + log_warn("Potential replay attack detected"); + return PMR_ERR_SEMANTIC; + } + } + } + + /* no corresponding identity to process the message => the message is + * not meant for us */ + if (!identity) { + /* if forwarding succeeded */ + if (!message_forward(message, + json_message, + sender, + global_state)) { + /* store message's nonce */ + nonce_store(&sender_peer->nonces, nonce_value); + } + return PMR_DONE; + } else if (identity->flags & IDENTITY_TMP) { + /* no one is supposed to send a message to this identity */ + return PMR_ERR_SEMANTIC; + } + + /* the message is meant for us; process it */ + + switch (msg_type) { + case P2P_BYE: + res = process_p2p_bye(message, + json_message, + sender, + sender_peer, + global_state); + break; + case P2P_HELLO: + res = process_p2p_hello(message, + sender, + neighbours, + global_state->port); + break; + case P2P_PEERS_ADV: + /* if the message was not sent by the same peer + * pseudonym of the sender as their p2p.hello */ + if (&sender->pseudonym != sender_peer) { + log_debug("process_message - incorrect sender"); + /* since the message was sent by other ID than + * neighbour's pseudonym, we've treated it as a + * new peer; that's unwanted for n2n messages */ + peer_delete(sender_peer); + return PMR_ERR_SEMANTIC; + } + res = process_p2p_peers_adv(message, sender, hosts); + break; + case P2P_PEERS_SOL: + res = process_p2p_peers_sol(sender, hosts); + break; + case P2P_PING: + res = process_p2p_ping(sender); + break; + case P2P_PONG: + if (&sender->pseudonym != sender_peer) { + log_debug("process_message - incorrect sender"); + peer_delete(sender_peer); + return PMR_ERR_SEMANTIC; + } + res = process_p2p_pong(sender); + break; + case P2P_ROUTE_ADV: + res = process_p2p_route_adv(message, + json_message, + sender, + sender_peer, + global_state); + break; + case P2P_ROUTE_SOL: + res = process_p2p_route_sol(message, + json_message, + sender, + global_state); + break; + default: + log_debug("process_message - unknown message type"); + return PMR_ERR_SEMANTIC; + } + + /* if the message processing has failed */ + if (res) { + return PMR_ERR_INTERNAL; + } + + /* sucessfully processed; store message's nonce */ + if (msg_type == P2P_ROUTE_ADV) { + presence_nonce_store(sender_peer, nonce_value); + } else if (msg_type != P2P_BYE) { + nonce_store(&sender_peer->nonces, nonce_value); + } + + sender->failed_pings = 0; + + return PMR_DONE; +} + +/** + * Process p2p.bye. + * + * @param message Process this message. + * @param json_message The received message in the JSON format. + * @param sender We've received the message from this neighbour. + * @param sender_peer Sender's peer representation. + * @param global_state The global state. + * + * @return 0 Successfully processed. + */ +static int process_p2p_bye(const message_t *message, + const char *json_message, + neighbour_t *sender, + peer_t *sender_peer, + global_state_t *global_state) +{ + route_delete(&global_state->routing_table, message->from); + peer_delete(sender_peer); + + message_forward(message, json_message, sender, global_state); + + return 0; +} + +/** + * Process p2p.hello. + * + * @param message Process this message. + * @param sender We've received the message from this neighbour. + * @param neighbours Our neighbours. + * @param port Our listening port. + * + * @return 0 Successfully processed. + * @return 1 Failure. + */ +static int process_p2p_hello(const message_t *message, + neighbour_t *sender, + linkedlist_t *neighbours, + unsigned short port) +{ + p2p_hello_t *hello; + neighbour_t *neighbour; + + /* don't take hello from already active neighbour */ + if (sender->flags & NEIGHBOUR_ACTIVE) { + return 1; + } + + /* check self-neighbouring; if we have a neighbour with 'my_pseudonym' + * the same as the received message's sender ID, then we've + * detected self-neighbouring; delete the mentioned neighbour, + * and also delete the message sender (our neighbour representation of + * the sending counterparty) + */ + if ((neighbour = find_neighbour(neighbours, + message->from, + compare_neighbour_my_pseudonyms))) { + linkedlist_delete_safely(neighbour->node, clear_neighbour); + linkedlist_delete_safely(sender->node, clear_neighbour); + return 1; + } + + hello = (p2p_hello_t *) message->body.data; + + sender->client = (char *) malloc((strlen(hello->client) + 1) * + sizeof(char)); + if (!sender->client) { + log_error("Processing p2p.hello"); + return 1; + } + strcpy(sender->client, hello->client); + + /* if the new neighbour is not one of the default hosts */ + if (sender->host) { + sender->host->port = hello->port; + } + + memcpy(sender->pseudonym.identifier, + message->from, + crypto_box_PUBLICKEYBYTES); + + set_neighbour_flags(sender, NEIGHBOUR_ACTIVE); + + return send_p2p_hello(sender, port); +} + +/** + * Process p2p.peers.adv. + * + * @param message Process this message. + * @param sender We've received the message from this neighbour. + * @param hosts Our known hosts. + * + * @return 0 Successfully processed. + * @return 1 Failure. + */ +static int process_p2p_peers_adv(const message_t *message, + neighbour_t *sender, + linkedlist_t *hosts) +{ + struct in6_addr addr; + char addr_str[INET6_ADDRSTRLEN]; + size_t n; + p2p_peers_adv_t *peers_adv; + unsigned short port; + char *pos; + char tuple[55]; /* string address = 45 chars, + * string port = 5 chars, + * ',' + 3x' ' + '\0' = 5 chars, + * total = 55 chars + */ + + /* if we haven't asked this neighbour for addresses */ + if (!(sender->flags & NEIGHBOUR_ADDRS_REQ)) { + log_debug("process_p2p_peers_adv - unwanted addrs arrived"); + return 1; + } + unset_neighbour_flags(sender, NEIGHBOUR_ADDRS_REQ); + + peers_adv = (p2p_peers_adv_t *) message->body.data; + + /* if the list doesn't have enough chars to be even empty ("[ ]", + * which is 4 chars) */ + if (strlen(peers_adv->addresses) < 4) { + log_debug("process_p2p_peers_adv - wrong addrs format"); + return 1; + } + + /* set initial pointer position; skip the outer list '[' */ + pos = peers_adv->addresses + 1; + /* get position of the first '[' */ + pos = strchr(pos, '['); + + /* while there is a tuple to be processed */ + while (pos) { + /* get the number of chars between the current '[' and the next + * ']', both boundary chars excluding */ + n = strcspn(++pos, "]"); + + /* the tuple can not have more than 54 chars (plus '\0') */ + if (n > 54) { + log_debug("process_p2p_peers_adv - wrong addrs format"); + return 1; + } + + /* copy the chars between '[' and ']', both boundary + * chars excluding */ + strncpy(tuple, pos, n); + tuple[n] = '\0'; + + /* get string addr and numerical port from the tuple */ + if (sscanf(tuple, " %[^,], %hu ", addr_str, &port) != 2) { + log_debug("process_p2p_peers_adv - wrong addrs format"); + return 1; + } + + /* if conversion of addr_str into addr succeeded */ + if (inet_pton(AF_INET6, addr_str, &addr) == 1) { + save_host(hosts, &addr, port, HOST_AVAILABLE); + } + + /* go to next tuple */ + pos = strchr(pos, '['); + } + + return 0; +} + +/** + * Process p2p.peers.sol. + * + * @param sender We've received the message from this neighbour. + * @param hosts Hosts known to us. + * + * @return 0 Successfully processed. + * @return 1 Failure. + */ +static int process_p2p_peers_sol(neighbour_t *sender, const linkedlist_t *hosts) +{ + return send_p2p_peers_adv(sender, hosts); +} + +/** + * Process p2p.ping. + * + * @param sender We've received the message from this neighbour. + * + * @return 0 Successfully processed. + * @return 1 Failure. + */ +static int process_p2p_ping(neighbour_t *sender) +{ + return send_p2p_pong(sender); +} + +/** + * Process p2p.pong. + * + * @param sender We've received the message from this neighbour. + * + * @return 0 Successfully processed. + */ +static int process_p2p_pong(neighbour_t *sender) +{ + sender->failed_pings = 0; + + return 0; +} + +/** + * Process p2p.route.adv. + * + * @param message Process this message. + * @param json_message The received message in the JSON format. + * @param sender We've received the message from this neighbour. + * @param sender_peer Sender's peer representation. + * @param global_state The global state. + * + * @return 0 Successfully processed. + * @return 1 Failure. + */ +static int process_p2p_route_adv(const message_t *message, + const char *json_message, + neighbour_t *sender, + peer_t *sender_peer, + global_state_t *global_state) +{ + route_t *route; + linkedlist_t *routing_table; + + routing_table = &global_state->routing_table; + + route = route_find(routing_table, message->from); + if (!route) { + if (!(route = route_add(routing_table, sender_peer, sender))) { + log_error("Adding a new route"); + return 1; + } + } + + route->last_update = time(NULL); + + /* if we are dealing with the newest p2p.route.adv */ + if (message->body.nonce > route->destination->presence_nonce.value) { + if (route_reset(route, sender)) { + log_error("Reseting a route"); + return 1; + } + /* otherwise just add another next hop */ + } else if (route_next_hop_add(route, sender)) { + log_error("Adding a new next hop"); + return 1; + } + + message_forward(message, json_message, sender, global_state); + return 0; +} + +/** + * Process p2p.route.sol. + * + * @param message Process this message. + * @param json_message The received message in the JSON format. + * @param sender We've received the message from this neighbour. + * @param global_state The global state. + * + * @return 0 Successfully processed. + * @return 1 Failure. + */ +static int process_p2p_route_sol(const message_t *message, + const char *json_message, + neighbour_t *sender, + global_state_t *global_state) +{ + identity_t *identity; + p2p_route_sol_t *route_sol; + + route_sol = (p2p_route_sol_t *) message->body.data; + identity = identity_find(&global_state->identities, route_sol->target); + + /* if we wouldn't rebroadcast the p2p.route.sol, and just sent + * a p2p.route.adv, it could be obvious the 'target' is us */ + if (message_forward(message, + json_message, + sender, + global_state)) { + return 1; + } + /* if someone's looking for us */ + if (identity) { + if (difftime(time(NULL), + identity->last_adv) > ADV_GAP_TIME) { + /* broadcast the identity */ + return send_p2p_route_adv( + &global_state->neighbours, + identity); + } + /* returning 1 means not storing the message's nonce, + * and so if we receive this message again, we can + * re-check the p2p.route.adv gap time */ + return 1; + } + + return 0; +} diff --git a/src/daemon_messages_processor.h b/src/daemon_messages_processor.h new file mode 100644 index 0000000000000000000000000000000000000000..9bc0effb467612e69d6374e9c24309c04fea7eea --- /dev/null +++ b/src/daemon_messages_processor.h @@ -0,0 +1,40 @@ +/* + * Coincer + * Copyright (C) 2017-2018 Coincer Developers + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#ifndef DAEMON_MESSAGES_PROCESSOR_H +#define DAEMON_MESSAGES_PROCESSOR_H + +#include "global_state.h" +#include "neighbours.h" + +/** Result of message processing. */ +enum process_message_result { + PMR_DONE, + PMR_ERR_INTEGRITY, + PMR_ERR_INTERNAL, + PMR_ERR_PARSING, + PMR_ERR_SEMANTIC, + PMR_ERR_VERSION +}; + +enum process_message_result + process_encoded_message(const char *json_message, + neighbour_t *sender, + global_state_t *global_state); + +#endif /* DAEMON_MESSAGES_PROCESSOR_H */ diff --git a/src/global_state.c b/src/global_state.c index d3af9facb2424e2ffc30103ca1a4f72f7fa54e0e..98203fed9f40d01e9af46d7f335327e7c0d26fd2 100644 --- a/src/global_state.c +++ b/src/global_state.c @@ -23,6 +23,8 @@ #include "log.h" #include "neighbours.h" #include "paths.h" +#include "peers.h" +#include "routing.h" /** * Clear global state variables. @@ -31,11 +33,15 @@ */ void global_state_clear(global_state_t *global_state) { + linkedlist_destroy(&global_state->routing_table, route_clear); linkedlist_destroy(&global_state->pending_neighbours, clear_neighbour); + linkedlist_destroy(&global_state->peers, peer_clear); linkedlist_destroy(&global_state->neighbours, clear_neighbour); + linkedlist_destroy(&global_state->message_traces, NULL); linkedlist_destroy(&global_state->hosts, NULL); /* event nodes' data are not malloc'd; just apply the removal */ linkedlist_apply(&global_state->events, event_free, linkedlist_remove); + linkedlist_destroy(&global_state->identities, NULL); clear_paths(&global_state->filepaths); event_base_free(global_state->event_loop); @@ -54,10 +60,23 @@ int global_state_init(global_state_t *global_state) return 1; } + linkedlist_init(&global_state->identities); + if (!(global_state->true_identity = identity_generate(0x00))) { + log_error("Creating our true identity"); + return 1; + } + linkedlist_append(&global_state->identities, + global_state->true_identity); + linkedlist_init(&global_state->events); linkedlist_init(&global_state->hosts); + linkedlist_init(&global_state->message_traces); linkedlist_init(&global_state->neighbours); + linkedlist_init(&global_state->peers); linkedlist_init(&global_state->pending_neighbours); + linkedlist_init(&global_state->routing_table); + + global_state->port = 0; return 0; } diff --git a/src/global_state.h b/src/global_state.h index 2054cd7dc7632f52195702ccc0c7d20545245585..9515ae4471ecf753e3ac64f5d5beb2e72162a36b 100644 --- a/src/global_state.h +++ b/src/global_state.h @@ -23,6 +23,7 @@ #include "linkedlist.h" #include "paths.h" +#include "peers.h" /** * Event loop works with the data stored in an instance of this struct. @@ -36,10 +37,23 @@ typedef struct s_global_state { filepaths_t filepaths; /** Linked list of some known hosts in the network. */ linkedlist_t hosts; + /** List of our identities. */ + linkedlist_t identities; + /** Linked list of recently received message traces (their hash and + * a pointer to a neighbour who's sent us the message). */ + linkedlist_t message_traces; /** Linked list of our neighbours. */ linkedlist_t neighbours; + /** Known peers. */ + linkedlist_t peers; /** Hosts that didn't accept/reject us yet. */ linkedlist_t pending_neighbours; + /** We listen on this port. */ + unsigned short port; + /** Routes to hosts. */ + linkedlist_t routing_table; + /** Our true identity. */ + identity_t *true_identity; } global_state_t; void global_state_clear(global_state_t *global_state); diff --git a/src/hosts.c b/src/hosts.c index 8df6eb479d9eece7afa7d49e60c97b31e4501325..ff56f278adb27e0d56e94151f0db83e0c02b2e1d 100644 --- a/src/hosts.c +++ b/src/hosts.c @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#define _BSD_SOURCE /* snprintf */ + #include #include #include @@ -27,17 +29,6 @@ #include "linkedlist.h" #include "log.h" -/** - * Delete all hosts and their data. - * - * @param hosts Linkedlist of hosts. - */ -void clear_hosts(linkedlist_t *hosts) -{ - /* host_t has no dynamically allocated variables */ - linkedlist_destroy(hosts, NULL); -} - /** * Fetch hosts from file into linkedlist. * @@ -49,7 +40,7 @@ void clear_hosts(linkedlist_t *hosts) */ int fetch_hosts(const char *hosts_path, linkedlist_t *hosts) { - struct in6_addr addr; + host_t current_host; FILE *hosts_file; hosts_file = fopen(hosts_path, "rb"); @@ -59,8 +50,11 @@ int fetch_hosts(const char *hosts_path, linkedlist_t *hosts) return 1; } - while (fread(&addr, sizeof(struct in6_addr), 1, hosts_file) == 1) { - save_host(hosts, &addr); + while (fread(¤t_host, sizeof(host_t), 1, hosts_file) == 1) { + save_host(hosts, + ¤t_host.addr, + current_host.port, + current_host.flags); } fclose(hosts_file); @@ -144,64 +138,118 @@ host_t *find_host(const linkedlist_t *hosts, } /** - * '\n' separated output string of host addresses in readable form. + * Fetch output string of hosts. For every host get its address in readable + * form and its port. Output format: [ [ addr, port ], ... ]. * - * @param hosts List of hosts. - * @param output Output string. + * @param hosts List of hosts. + * @param output '\0' terminated, dynamically allocated, output + * string of hosts. * - * @return 0 Success. - * @return 1 Failure. + * @return 0 Success. + * @return 1 Failure. */ int hosts_to_str(const linkedlist_t *hosts, char **output) { - linkedlist_node_t *current_node; + const char *closing = " ]"; host_t *current_host; - size_t output_size = 0; + linkedlist_node_t *current_node; + char line[64]; + const char *opening = "[ "; + size_t opening_len; + size_t output_buff_size; + size_t output_size; + const char *separator = ", "; + size_t separator_len; char text_ip[INET6_ADDRSTRLEN]; + char *tmp_out; + + opening_len = strlen(opening); + separator_len = strlen(separator); - /* TODO: Don't assume any buffer size. This will be fixed in the - * next commit. */ - *output = (char *) malloc(4096 * sizeof(char)); + output_size = 0; + output_buff_size = 1024; + + *output = (char *) malloc(output_buff_size * sizeof(char)); if (!*output) { - log_error("Hosts to string"); + log_error("Allocating string of hosts"); return 1; } - *output[0] = '\0'; + (*output)[output_size++] = '\0'; + + /* opening of the outer list */ + strcat(*output, opening); + output_size += opening_len; + current_node = linkedlist_get_first(hosts); - while (current_node != NULL) { + while (current_node) { current_host = (host_t *) current_node->data; + /* IPv6 can have max 45 chars, port 5, both opening and closing + * of the inner list 2 each, the separator 2, possible closing + * of the outer list 2, and '\0' 1 => max 59 chars, hence 128 + * has to be enough as a buffer size reserve. */ + if (output_buff_size - output_size < 128) { + output_buff_size *= 2; + + tmp_out = (char *) realloc(*output, output_buff_size * + sizeof(char)); + if (!tmp_out) { + log_error("Reallocating string of hosts"); + free(output); + return 1; + } + *output = tmp_out; + } + /* binary ip to text ip conversion */ inet_ntop(AF_INET6, ¤t_host->addr, text_ip, INET6_ADDRSTRLEN); - output_size += strlen(text_ip); - strcat(*output, text_ip); current_node = linkedlist_get_next(hosts, current_node); - /* if it's not the last host, append '\n' */ - if (current_node != NULL) { - *output[output_size++] = '\n'; + + /* [ addr, port ] into 'line', 64 has to be enough from + * the reason described above */ + if (snprintf(line, 64, "%s%s%s%hu%s", opening, + text_ip, + separator, + current_host->port, + closing) <= 59) { + + strcat(*output, line); + output_size += strlen(line); + /* not the last host => append inner lists separator */ + if (current_node) { + strcat(*output, separator); + output_size += separator_len; + } } - *output[output_size] = '\0'; } + /* closing of the outer list */ + strcat(*output, closing); + return 0; } /** * Save new host into sorted linkedlist of hosts. * - * @param hosts The linkedlist of hosts. - * @param addr Address of the new host. + * @param hosts The linkedlist of hosts. + * @param addr Address of the new host. + * @param port Host's listening port. + * @param flags Host's flags. * - * @return host_t Newly saved host. - * @return NULL Host is already saved, default or - * allocation failure. + * @return host_t Newly saved host. + * @return NULL Host is already saved, default or + * allocation failure. */ -host_t *save_host(linkedlist_t *hosts, const struct in6_addr *addr) +host_t *save_host(linkedlist_t *hosts, + const struct in6_addr *addr, + unsigned short port, + int flags) { int cmp_value; struct in6_addr curr_addr; @@ -230,7 +278,9 @@ host_t *save_host(linkedlist_t *hosts, const struct in6_addr *addr) /* initialize all attributes of the new host */ memcpy(&new_host->addr, addr, 16); - set_host_flags(new_host, HOST_AVAILABLE); + new_host->port = port; + new_host->flags = 0x0; + set_host_flags(new_host, flags); /* get textual representation of 'addr' */ inet_ntop(AF_INET6, addr, text_ip, INET6_ADDRSTRLEN); @@ -330,8 +380,8 @@ int store_hosts(const char *hosts_path, const linkedlist_t *hosts) while (current) { current_host = (host_t *) current->data; /* if fwrite fails, terminate storing */ - if (fwrite(¤t_host->addr, - sizeof(struct in6_addr), + if (fwrite(current_host, + sizeof(host_t), 1, hosts_file) != 1) { log_error("Storing hosts"); diff --git a/src/hosts.h b/src/hosts.h index 4996e16ffc8101dc390295ac02fee2029cc68f9e..b2793f1100c68f2646b9cabca3a170895f94705f 100644 --- a/src/hosts.h +++ b/src/hosts.h @@ -56,11 +56,11 @@ typedef struct s_host { struct in6_addr addr; /** A set of flags for this host. */ int flags; + /* Host's listening port. */ + unsigned short port; /* TODO: add uptime */ } host_t; -void clear_hosts(linkedlist_t *hosts); - int fetch_hosts(const char *hosts_path, linkedlist_t *hosts); int fetch_specific_hosts(const linkedlist_t *hosts, @@ -74,7 +74,10 @@ int hosts_to_str(const linkedlist_t *hosts, char **output); void reset_hosts_availability(linkedlist_t *hosts); -host_t *save_host(linkedlist_t *hosts, const struct in6_addr *addr); +host_t *save_host(linkedlist_t *hosts, + const struct in6_addr *addr, + unsigned short port, + int flags); void set_host_flags(host_t *host, int flags); diff --git a/src/json_parser.c b/src/json_parser.c index 88d5359ac8f435be284e0bd335ca68bff6639494..70d93f12af4048b773f5258e8c406a762ebe1db2 100644 --- a/src/json_parser.c +++ b/src/json_parser.c @@ -29,13 +29,16 @@ /** * Decode a JSON message (including its JSON body) into daemon message. * - * @param json_message Decode this JSON message. - * @param message Store decoded data in here. + * @param json_message Decode this JSON message. + * @param message Store decoded data in here. + * @param json_message_body Store the message's JSON body in here. * - * @return 0 Decoding successful. - * @return 1 Failure. + * @return 0 Decoding successful. + * @return 1 Failure. */ -int decode_message(const char *json_message, message_t *message) +int decode_message(const char *json_message, + message_t *message, + char **json_message_body) { char from_hex[65]; char to_hex[65]; diff --git a/src/json_parser.h b/src/json_parser.h index 21c5635e238cbee49d30b7b8d8d40543cd1358c7..36b53663431c8dca0c8c291c657b2baadcaef3e9 100644 --- a/src/json_parser.h +++ b/src/json_parser.h @@ -34,7 +34,9 @@ static const char *msg_type_str[] = { "p2p.route.sol" }; -int decode_message(const char *json_message, message_t *message); +int decode_message(const char *json_message, + message_t *message, + char **json_message_body); int decode_message_body(const char *json_body, message_body_t *body); int encode_message(const message_t *message, char **json_message); int encode_message_body(const message_body_t *body, char **json_body); diff --git a/src/neighbours.c b/src/neighbours.c index 5dbebc764717093c203e98170a8d542cc05efde1..08c58bbeab8249e4dc41e0903897a3bb552538b0 100644 --- a/src/neighbours.c +++ b/src/neighbours.c @@ -19,13 +19,16 @@ #include #include #include +#include #include #include #include +#include "crypto.h" #include "linkedlist.h" #include "log.h" #include "neighbours.h" +#include "peers.h" /** * Add new neighbour into neighbours. @@ -62,9 +65,21 @@ neighbour_t *add_new_neighbour(linkedlist_t *neighbours, /* initialize new neighbour */ memcpy(&new_neighbour->addr, addr, sizeof(struct in6_addr)); new_neighbour->buffer_event = bev; + new_neighbour->client = NULL; new_neighbour->failed_pings = 0; new_neighbour->flags = 0x0; new_neighbour->host = NULL; + /* create our pseudonym */ + generate_keypair(&new_neighbour->my_pseudonym.keypair); + new_neighbour->my_pseudonym.nonce_value = get_random_uint64_t() >> 1; + /* initialize neighbour's pseudonym */ + memset(new_neighbour->pseudonym.identifier, + 0x0, + crypto_box_PUBLICKEYBYTES); + linkedlist_init(&new_neighbour->pseudonym.nonces); + memset(&new_neighbour->pseudonym.presence_nonce, + 0x0, + sizeof(nonce_t)); if (!(new_neighbour->node = linkedlist_append(neighbours, new_neighbour))) { @@ -84,6 +99,10 @@ neighbour_t *add_new_neighbour(linkedlist_t *neighbours, void clear_neighbour(neighbour_t *neighbour) { bufferevent_free(neighbour->buffer_event); + if (neighbour->client) { + free(neighbour->client); + } + peer_clear(&neighbour->pseudonym); } /** @@ -117,6 +136,42 @@ int compare_neighbour_bufferevents(const neighbour_t *neighbour, return neighbour->buffer_event != bev; } +/** + * Comparing function between our neighbour-specific pseudonym and a public key. + * + * @param neighbour Use this neighbour. + * @param public_key Compare to this public key. + * + * @return 0 The public keys equal. + * @return <0 'public_key' is greater. + * @return >0 The pseudonym's public key is greater. + */ +int compare_neighbour_my_pseudonyms(const neighbour_t *neighbour, + unsigned char *public_key) +{ + return memcmp(neighbour->my_pseudonym.keypair.public_key, + public_key, + crypto_box_PUBLICKEYBYTES); +} + +/** + * Comparing function between neighbour's pseudonym and a public key. + * + * @param neighbour Use this neighbour. + * @param public_key Compare to this public key. + * + * @return 0 The public keys equal. + * @return <0 'public_key' is greater. + * @return >0 The pseudonym's public key is greater. + */ +int compare_neighbour_pseudonyms(const neighbour_t *neighbour, + unsigned char *public_key) +{ + return memcmp(neighbour->pseudonym.identifier, + public_key, + crypto_box_PUBLICKEYBYTES); +} + /** * Fetch pointers to neighbours with specific flags set, into an array * that is being allocated in here. diff --git a/src/neighbours.h b/src/neighbours.h index 1757884893fe8a561f9c890a9b9c186a97ceecc0..332833b21b2f56e0ee1682670d5138b251b6a1e3 100644 --- a/src/neighbours.h +++ b/src/neighbours.h @@ -25,9 +25,13 @@ #include "hosts.h" #include "linkedlist.h" +#include "peers.h" /** Request for addresses. */ #define NEIGHBOUR_ADDRS_REQ 0x01 +/** Practically, this means that we've received p2p.hello from this + * neighbour (and so we know their pseudonym). */ +#define NEIGHBOUR_ACTIVE 0x02 /** Data type for the linkedlist of neighbours. */ typedef struct s_neighbour { @@ -36,14 +40,20 @@ typedef struct s_neighbour { struct in6_addr addr; /** Bufferevent belonging to this neighbour. */ struct bufferevent *buffer_event; + /** Client info. */ + char *client; /** Number of failed ping attempts -- max 3, then disconnect. */ size_t failed_pings; /** A set of flags for this neighbour. */ int flags; /** Corresponding host. */ host_t *host; + /** Our peer pseudonym for this neighbour. */ + identity_t my_pseudonym; /** Neighbour's node in the neighbours container. */ linkedlist_node_t *node; + /** Neighbour's peer pseudonym for us. */ + peer_t pseudonym; } neighbour_t; neighbour_t *add_new_neighbour(linkedlist_t *neighbours, @@ -58,6 +68,11 @@ int compare_neighbour_addrs(const neighbour_t *neighbour, int compare_neighbour_bufferevents(const neighbour_t *neighbour, const struct bufferevent *bev); +int compare_neighbour_my_pseudonyms(const neighbour_t *neighbour, + unsigned char *public_key); + +int compare_neighbour_pseudonyms(const neighbour_t *neighbour, + unsigned char *public_key); int fetch_specific_neighbours(const linkedlist_t *neighbours, neighbour_t ***output, diff --git a/src/p2p.c b/src/p2p.c index f0983c9596a2112a458de1f31f11b6f1bca30668..512c1a8bc033b11d8183028954adfc8992ba69d0 100644 --- a/src/p2p.c +++ b/src/p2p.c @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -#define _POSIX_SOURCE /* strtok_r */ - #include /* inet_ntop */ #include #include @@ -30,12 +28,14 @@ #include #include "crypto.h" +#include "daemon_messages_processor.h" #include "global_state.h" #include "hosts.h" #include "linkedlist.h" #include "log.h" #include "neighbours.h" #include "p2p.h" +#include "routing.h" /** * Simple helper for conversion of binary IP to readable IP address. @@ -48,50 +48,6 @@ static void ip_to_string(const struct in6_addr *binary_ip, char *ip) inet_ntop(AF_INET6, binary_ip, ip, INET6_ADDRSTRLEN); } -/** - * Ask a neighbour for a list of addresses. - * - * @param global_state Data for the event loop to work with. - * @param neighbour The neighbour to be asked. - */ -static void ask_for_addresses(neighbour_t *neighbour) -{ - struct bufferevent *bev; - - if (!neighbour || !(bev = neighbour->buffer_event)) { - return; - } - - /* send message "hosts" to the neighbour, as a request - * for the list of hosts; 6 is the number of bytes to be transmitted */ - evbuffer_add(bufferevent_get_output(bev), "hosts", 6); - /* accept addresses only from those neighbours that we've asked */ - set_neighbour_flags(neighbour, NEIGHBOUR_ADDRS_REQ); -} - -/** - * Process received list of host addresses. - * - * @param global_state Data for the event loop to work with. - * @param hosts '\n'-separated list of host addresses. - */ -static void process_hosts(global_state_t *global_state, char *hosts) -{ - struct in6_addr addr; - const char delim[2] = "\n"; - char *line; - char *save_ptr; - - line = strtok_r(hosts, delim, &save_ptr); - - while (line) { - if (inet_pton(AF_INET6, line, &addr) == 1) { - save_host(&global_state->hosts, &addr); - } - line = strtok_r(NULL, delim, &save_ptr); - } -} - /** * Processing a P2P message. * @@ -100,14 +56,11 @@ static void process_hosts(global_state_t *global_state, char *hosts) */ static void p2p_process(struct bufferevent *bev, void *ctx) { - char *addrs = NULL; global_state_t *global_state; struct evbuffer *input; size_t len; - char *message; + char *json_message; neighbour_t *neighbour; - struct evbuffer *output; - char response[2048]; /* TODO: adjust size */ char text_ip[INET6_ADDRSTRLEN]; global_state = (global_state_t *) ctx; @@ -118,67 +71,37 @@ static void p2p_process(struct bufferevent *bev, void *ctx) compare_neighbour_bufferevents); assert(neighbour != NULL); - /* reset neighbour's failed pings */ - neighbour->failed_pings = 0; - - /* read from the input buffer, write to output buffer */ - input = bufferevent_get_input(bev); - output = bufferevent_get_output(bev); + /* read from the input buffer */ + input = bufferevent_get_input(bev); /* get length of the input message */ len = evbuffer_get_length(input); /* allocate memory for the input message including '\0' */ - message = (char *) malloc((len + 1) * sizeof(char)); - if (!message) { + json_message = (char *) malloc((len + 1) * sizeof(char)); + if (!json_message) { log_error("Received message allocation"); return; } /* drain input buffer into data; -1 if evbuffer_remove failed */ - if (evbuffer_remove(input, message, len) == -1) { - free(message); + if (evbuffer_remove(input, json_message, len) == -1) { + free(json_message); return; } else { - message[len] = '\0'; + json_message[len] = '\0'; } ip_to_string(&neighbour->addr, text_ip); - log_debug("p2p_process - received: %s from %s", message, text_ip); - - response[0] = '\0'; - - /* TODO: Replace with JSON messages */ - if (strcmp(message, "ping") == 0) { - strcpy(response, "pong"); - /* ignore "pong" */ - } else if (strcmp(message, "pong") == 0) { - /* "hosts" is a request for list of addresses */ - } else if (strcmp(message, "hosts") == 0) { - hosts_to_str(&global_state->hosts, &addrs); - /* list of addresses */ - } else { - if (neighbour->flags & NEIGHBOUR_ADDRS_REQ) { - process_hosts(global_state, message); - unset_neighbour_flags(neighbour, NEIGHBOUR_ADDRS_REQ); - } - } + log_debug("p2p_process - received: %s from %s", json_message, text_ip); - if (response[0] != '\0') { - log_debug("p2p_process - responding with: %s", response); - /* copy response to the output buffer */ - evbuffer_add_printf(output, "%s", response); - /* note: this will be replaced in the next commit */ - } else if (addrs) { - log_debug("p2p_process - responding with: %s", addrs); - /* copy response to the output buffer */ - evbuffer_add_printf(output, "%s", addrs); - - free(addrs); + if (process_encoded_message(json_message, + neighbour, + global_state) != PMR_DONE) { + log_warn("Message processing has failed"); } - /* deallocate input message */ - free(message); + free(json_message); } /** @@ -186,9 +109,11 @@ static void p2p_process(struct bufferevent *bev, void *ctx) * * @param neighbours Linked list of neighbours. * @param neighbour Timeout invoked on this neighbour. + * @param routing_table Routing table. */ static void timeout_process(linkedlist_t *neighbours, - neighbour_t *neighbour) + neighbour_t *neighbour, + linkedlist_t *routing_table) { char text_ip[INET6_ADDRSTRLEN]; @@ -204,14 +129,12 @@ static void timeout_process(linkedlist_t *neighbours, log_debug("timeout_process - sending ping to %s. " "Failed pings: %lu", text_ip, neighbour->failed_pings); - /* send ping to the inactive neighbour; - * 5 is the length of bytes to be transmitted */ - evbuffer_add(bufferevent_get_output(neighbour->buffer_event), - "ping", 5); - + send_p2p_ping(neighbour); neighbour->failed_pings++; } else { log_info("%s timed out", text_ip); + /* remove this neighbour (next hop) from all routes */ + routing_table_remove_next_hop(routing_table, neighbour); linkedlist_delete_safely(neighbour->node, clear_neighbour); } } @@ -246,13 +169,17 @@ static void process_pending_neighbour(global_state_t *global_state, /* move the neighbour from pending into neighbours */ linkedlist_move(neighbour->node, &global_state->neighbours); + /* send p2p.hello */ + send_p2p_hello(neighbour, global_state->port); + needed_conns = MIN_NEIGHBOURS - linkedlist_size(&global_state->neighbours); /* if we need more neighbours */ if (needed_conns > 0) { /* and we don't have enough hosts available */ if (available_hosts_size < needed_conns) { - ask_for_addresses(neighbour); + /* ask for some */ + send_p2p_peers_sol(neighbour); } } /* connecting to the neighbour was unsuccessful */ @@ -282,13 +209,23 @@ static void process_neighbour(global_state_t *global_state, if (events & BEV_EVENT_ERROR) { log_info("Connection error, removing %s", text_ip); + + /* remove this neighbour (next hop) from all routes */ + routing_table_remove_next_hop(&global_state->routing_table, + neighbour); linkedlist_delete_safely(neighbour->node, clear_neighbour); } else if (events & BEV_EVENT_EOF) { log_info("%s disconnected", text_ip); + + /* remove this neighbour (next hop) from all routes */ + routing_table_remove_next_hop(&global_state->routing_table, + neighbour); linkedlist_delete_safely(neighbour->node, clear_neighbour); /* timeout flag on 'bev' */ } else if (events & BEV_EVENT_TIMEOUT) { - timeout_process(&global_state->neighbours, neighbour); + timeout_process(&global_state->neighbours, + neighbour, + &global_state->routing_table); } } @@ -341,6 +278,7 @@ static void accept_connection(struct evconnlistener *listener, neighbour_t *neigh; struct in6_addr *new_addr; host_t *host; + unsigned short port; char text_ip[INET6_ADDRSTRLEN]; struct timeval timeout; @@ -349,6 +287,7 @@ static void accept_connection(struct evconnlistener *listener, /* put binary representation of IP to 'new_addr' */ new_addr = &addr_in6->sin6_addr; + port = addr_in6->sin6_port; ip_to_string(new_addr, text_ip); @@ -390,7 +329,7 @@ static void accept_connection(struct evconnlistener *listener, log_info("New connection from [%s]:%d", text_ip, ntohs(addr_in6->sin6_port)); - host = save_host(&global_state->hosts, new_addr); + host = save_host(&global_state->hosts, new_addr, port, 0x0); if (!host) { host = find_host(&global_state->hosts, new_addr); } @@ -441,7 +380,7 @@ int listen_init(struct evconnlistener **listener, struct event_base **base = &global_state->event_loop; struct sockaddr_in6 sock_addr; - int port = DEFAULT_PORT; + int port = global_state->port = DEFAULT_PORT; *base = event_base_new(); if (!*base) { @@ -473,18 +412,20 @@ int listen_init(struct evconnlistener **listener, } /** - * Attempt to connect to a particular addr. + * Attempt to connect to a particular host. * * @param global_state Data for the event loop to work with. * @param addr Binary IP of a host that we want to connect to. + * @param port Host's listening port. * * @return 0 The connection attempt was successful. * @return 1 The host is already our neighbour. * @return 2 The host is pending to become our neighbour. * @return 3 Adding new pending neighbour unsuccessful. */ -int connect_to_addr(global_state_t *global_state, - const struct in6_addr *addr) +int connect_to_host(global_state_t *global_state, + const struct in6_addr *addr, + unsigned short port) { struct bufferevent *bev; host_t *host; @@ -516,8 +457,8 @@ int connect_to_addr(global_state_t *global_state, memset(&sock_addr6, 0x0, sizeof(sock_addr6)); sock_addr6.sin6_family = AF_INET6; - sock_addr6.sin6_port = htons(DEFAULT_PORT); - memcpy(&sock_addr6.sin6_addr, addr, 16); + sock_addr6.sin6_port = htons(port); + memcpy(&sock_addr6.sin6_addr, addr, sizeof(struct in6_addr)); sock_addr = (struct sockaddr *) &sock_addr6; sock_len = sizeof(sock_addr6); @@ -603,14 +544,14 @@ void add_more_connections(global_state_t *global_state, size_t conns_amount) "connecting to a default host..."); /* choose random default host */ idx = get_random_uint32_t(DEFAULT_HOSTS_SIZE); - memcpy(&addr, DEFAULT_HOSTS[idx], 16); + memcpy(&addr, DEFAULT_HOSTS[idx], sizeof(struct in6_addr)); /* if the host becomes our neighbour, and we need more * connections, get a list of hosts from them and attempt to * connect to them; it's our goal to use as few default * hosts as possible */ - result = connect_to_addr(global_state, &addr); + result = connect_to_host(global_state, &addr, DEFAULT_PORT); /* the host is already our neighbour; ask them for more addrs */ if (result == 1) { @@ -618,7 +559,7 @@ void add_more_connections(global_state_t *global_state, size_t conns_amount) &addr, compare_neighbour_addrs); assert(neigh != NULL); - ask_for_addresses(neigh); + send_p2p_peers_sol(neigh); log_debug("add_more_connections - " "asking for hosts"); } @@ -635,8 +576,9 @@ void add_more_connections(global_state_t *global_state, size_t conns_amount) idx = cur_conn_attempts; selected_host = available_hosts[idx]; /* perform a connection attempt */ - connect_to_addr(global_state, - &selected_host->addr); + connect_to_host(global_state, + &selected_host->addr, + selected_host->port); cur_conn_attempts++; } } diff --git a/src/p2p.h b/src/p2p.h index 232d1e860f178c5dd0c52a49c39b8a5ffd4af73a..0f3acaf0ad8aa28fac09215f40980fd760612250 100644 --- a/src/p2p.h +++ b/src/p2p.h @@ -33,8 +33,9 @@ void add_more_connections(global_state_t *global_state, size_t conns_amount); -int connect_to_addr(global_state_t *global_state, - const struct in6_addr *addr); +int connect_to_host(global_state_t *global_state, + const struct in6_addr *addr, + unsigned short port); int listen_init(struct evconnlistener **listener, global_state_t *global_state); diff --git a/src/peers.c b/src/peers.c new file mode 100644 index 0000000000000000000000000000000000000000..abea46ce4d4e254a0107a63bd6079f59d503940a --- /dev/null +++ b/src/peers.c @@ -0,0 +1,391 @@ +/* + * Coincer + * Copyright (C) 2017-2018 Coincer Developers + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "linkedlist.h" +#include "log.h" +#include "peers.h" + +/** + * Determine whether an identifier is empty (if all its bytes are set to 0x0). + * + * @param id Check this identifier. + * + * @return 1 The identifier is empty. + * @return 0 The identifier is not empty. + */ +int identifier_empty(const unsigned char id[crypto_box_PUBLICKEYBYTES]) +{ + return id[0] == 0x0 && !memcmp(id, + id + 1, + crypto_box_PUBLICKEYBYTES - 1); +} + +/** + * Find the identity for an identifier. + * + * @param identities List of our identities. + * @param identifier Search for this identifier. + * + * @return identity_t Corresponding identity for the + * identifier. + * @return NULL The identifier doesn't belong to any + * of our identities. + */ +identity_t *identity_find(const linkedlist_t *identities, + const unsigned char *identifier) +{ + identity_t *identity; + const linkedlist_node_t *node; + + node = linkedlist_get_first(identities); + while (node) { + identity = (identity_t *) node->data; + + /* if the identifier represents one of our identities */ + if (!memcmp(identity->keypair.public_key, + identifier, + crypto_box_PUBLICKEYBYTES)) { + return identity; + } + + node = linkedlist_get_next(identities, node); + } + + return NULL; +} + +/** + * Set flags on given identity. + * + * @param identity Set flags on this identity. + * @param flags Set these flags on the identity. + */ +void identity_flags_set(identity_t *identity, int flags) +{ + identity->flags |= flags; +} + +/** + * Unset flags on given identity. + * + * @param identity Unset flags on this identity. + * @param flags Unset these flags on the identity. + */ +void identity_flags_unset(identity_t *identity, int flags) +{ + identity->flags &= ~flags; +} + +/** + * Generate an identity, including its initial nonce value. + * + * @param flags The flags of the identity. + * + * @return identity_t Dynamically allocated identity. + * @return NULL Allocation failure. + */ +identity_t *identity_generate(int flags) +{ + identity_t *identity; + + identity = (identity_t *) malloc(sizeof(identity_t)); + if (!identity) { + log_error("Creating a new identity"); + return NULL; + } + + generate_keypair(&identity->keypair); + identity->nonce_value = get_random_uint64_t(); + identity->last_adv = 0; + identity->flags = 0x0; + identity_flags_set(identity, flags); + + return identity; +} + +/** + * Nonce staleness predicate. + * + * @param nonce Check staleness of this nonce. + * @param current_time The time to compare to. + * + * @return 1 The nonce is stale. + * @return 0 The nonce is not stale. + */ +int nonce_is_stale(const nonce_t *nonce, const time_t current_time) +{ + return difftime(current_time, nonce->creation) >= NONCE_STALE_TIME; +} + +/** + * Store a nonce into ascendingly sorted (by nonce value) container of nonces. + * + * @param nonces The nonces container. + * @param value The nonce's value. + * + * @return 0 Successfully stored. + * @return 1 Failure. + */ +int nonce_store(linkedlist_t *nonces, uint64_t value) +{ + linkedlist_node_t *cur_node; + nonce_t *cur_nonce; + nonce_t *nonce; + + nonce = (nonce_t *) malloc(sizeof(nonce_t)); + if (!nonce) { + log_error("Creating a nonce"); + return 1; + } + + nonce->value = value; + nonce->creation = time(NULL); + + /* place the nonce into appropriate position */ + cur_node = linkedlist_get_last(nonces); + while (cur_node) { + cur_nonce = (nonce_t *) cur_node->data; + /* appropriate position found */ + if (value > cur_nonce->value) { + if (cur_node != linkedlist_get_last(nonces)) { + nonce->creation = cur_nonce->creation; + } + + if (!linkedlist_insert_after(nonces, cur_node, nonce)) { + log_error("Storing a nonce"); + free(nonce); + return 1; + } + + return 0; + } + + cur_node = linkedlist_get_prev(nonces, cur_node); + } + + /* at this point we know the nonce should be placed at the beginning + * of the container; do that only if the container is empty */ + if (!linkedlist_empty(nonces) || + !linkedlist_insert_after(nonces, &nonces->first, nonce)) { + log_error("Storing a nonce"); + free(nonce); + return 1; + } + + return 0; +} + +/** + * Find nonce by its value in a list of nonces. + * + * @param nonces Choose the nonce from these nonces. + * @param value Nonce's value. + * + * @return nonce_t Requested nonce. + * @return NULL The nonce is absent. + */ +nonce_t *nonces_find(const linkedlist_t *nonces, uint64_t value) +{ + const linkedlist_node_t *node; + nonce_t *nonce; + + node = linkedlist_get_first(nonces); + while (node) { + nonce = (nonce_t *) node->data; + if (nonce->value == value) { + return nonce; + /* the nonces are ascendingly sorted */ + } else if (nonce->value > value) { + return NULL; + } + node = linkedlist_get_next(nonces, node); + } + + return NULL; +} + +/** + * Get the newest nonce (the nonce with the highest value) from a list + * of nonces. + * + * @param nonces Use these nonces. + * + * @return nonce_t The last nonce. + * @return NULL The nonces are empty. + */ +nonce_t *nonces_get_newest(const linkedlist_t *nonces) +{ + linkedlist_node_t *node; + if ((node = linkedlist_get_last(nonces))) { + return node->data; + } + return NULL; +} + +/** + * Get the oldest nonce (the nonce with the lowest value) from a list of nonces. + * + * @param nonces Use these nonces. + * + * @return nonce_t The first nonce. + * @return NULL The nonces are empty. + */ +nonce_t *nonces_get_oldest(const linkedlist_t *nonces) +{ + linkedlist_node_t *node; + if ((node = linkedlist_get_first(nonces))) { + return node->data; + } + return NULL; +} + +/** + * Remove stale nonces from a list of nonces. + * + * @param nonces Use these nonces. + */ +void nonces_remove_stale(linkedlist_t *nonces) +{ + time_t current_time; + linkedlist_node_t *node; + nonce_t *nonce; + + current_time = time(NULL); + + node = linkedlist_get_last(nonces); + /* skip the last nonce, as we always want at least one to be stored; + * if the 'node' is NULL, linkedlist_get_prev returns NULL */ + node = linkedlist_get_prev(nonces, node); + + while (node && node != &nonces->first) { + /* we are going to process this nonce */ + nonce = (nonce_t *) node->data; + /* and move to previous node */ + node = linkedlist_get_prev(nonces, node); + + if (nonce_is_stale(nonce, current_time)) { + /* remove the nonce's node; note that it is + * safe to assume 'node' isn't NULL, thanks to + * the usage of the linkedlist stub nodes */ + linkedlist_delete(node->next); + } + /* 'node' has already been moved to previous */ + } +} + +/** + * Safely delete peer's variables. + * + * @param peer Clear this peer. + */ +void peer_clear(peer_t *peer) +{ + linkedlist_destroy(&peer->nonces, NULL); +} + +/** + * Delete a peer from a list of peers. + * + * @param peer Delete this peer. + */ +void peer_delete(peer_t *peer) +{ + linkedlist_delete_safely(peer->node, peer_clear); +} + +/** + * Find a peer by its identifier in a list of peers. + * + * @param peers Use these peers. + * @param identifier Peer's identifier. + * + * @param peer_t The requested peer. + * @param NULL Unknown peer. + */ +peer_t *peer_find(const linkedlist_t *peers, + const unsigned char *identifier) +{ + const linkedlist_node_t *node; + peer_t *peer; + + node = linkedlist_get_first(peers); + while (node) { + peer = (peer_t *) node->data; + if (!memcmp(peer->identifier, + identifier, + crypto_box_PUBLICKEYBYTES)) { + return peer; + } + node = linkedlist_get_next(peers, node); + } + + return NULL; +} + +/** + * Store a new peer in a list of known peers. + * + * @param peers Store into this list. + * @param identifier The new peer's identifier. + * + * @return peer_t Newly stored peer. + * @return NULL Failure. + */ +peer_t *peer_store(linkedlist_t *peers, + const unsigned char *identifier) +{ + peer_t *new_peer; + + new_peer = (peer_t *) malloc(sizeof(peer_t)); + if (!new_peer) { + log_error("Allocating a new peer"); + return NULL; + } + + linkedlist_init(&new_peer->nonces); + memcpy(new_peer->identifier, identifier, crypto_box_PUBLICKEYBYTES); + memset(&new_peer->presence_nonce, 0x0, sizeof(nonce_t)); + + if (!(new_peer->node = linkedlist_append(peers, new_peer))) { + log_error("Storing a new peer"); + peer_clear(new_peer); + free(new_peer); + return NULL; + } + + return new_peer; +} + +/** + * Store a presence nonce of a peer. + * + * @param peer Use this peer. + * @param value The nonce's value. + */ +void presence_nonce_store(peer_t *peer, uint64_t value) +{ + peer->presence_nonce.value = value; + peer->presence_nonce.creation = time(NULL); +} diff --git a/src/peers.h b/src/peers.h new file mode 100644 index 0000000000000000000000000000000000000000..e87c09f176a4287bc1dca678fe2e66a2ec6b8f8c --- /dev/null +++ b/src/peers.h @@ -0,0 +1,92 @@ +/* + * Coincer + * Copyright (C) 2017-2018 Coincer Developers + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#ifndef PEERS_H +#define PEERS_H + +#include +#include +#include + +#include "crypto.h" +#include "linkedlist.h" + +/** After this many seconds we consider a nonce to be stale. */ +#define NONCE_STALE_TIME 60 + +/** If set, the identity will be removed ASAP. */ +#define IDENTITY_TMP 0x01 + +/** Nonce representation. */ +typedef struct s_nonce { + /** A number with maximum size of (2^64 - 1) representing a nonce. */ + uint64_t value; + /** Creation timestamp. */ + time_t creation; +} nonce_t; + +/** Peer representation. */ +typedef struct s_peer { + /** Peer's public key. */ + unsigned char identifier[crypto_box_PUBLICKEYBYTES]; + /** A sorted list of nonces tied to this peer. */ + linkedlist_t nonces; + /** The last known 'announcement of presence' nonce of this peer. */ + nonce_t presence_nonce; + /** The peer's node in the list of peers. */ + linkedlist_node_t *node; +} peer_t; + +/** Representation of one of our identities. */ +typedef struct s_identity { + /** A keypair including a public key as our identifier. */ + keypair_t keypair; + /** Flags of this identity. */ + int flags; + /** The time of the last p2p.route.adv of this identity.*/ + time_t last_adv; + /** The last used nonce with this identity. */ + uint64_t nonce_value; +} identity_t; + +int identifier_empty(const unsigned char id[crypto_box_PUBLICKEYBYTES]); + +identity_t *identity_find(const linkedlist_t *identities, + const unsigned char *identifier); +void identity_flags_set(identity_t *identity, int flags); +void identity_flags_unset(identity_t *identity, int flags); +identity_t *identity_generate(int flags); + +int nonce_is_stale(const nonce_t *nonce, const time_t current_time); +int nonce_store(linkedlist_t *nonces, uint64_t value); + +nonce_t *nonces_find(const linkedlist_t *nonces, uint64_t value); +nonce_t *nonces_get_newest(const linkedlist_t *nonces); +nonce_t *nonces_get_oldest(const linkedlist_t *nonces); +void nonces_remove_stale(linkedlist_t *nonces); + +void peer_clear(peer_t *peer); +void peer_delete(peer_t *peer); +peer_t *peer_find(const linkedlist_t *peers, + const unsigned char *identifier); +peer_t *peer_store(linkedlist_t *peers, + const unsigned char *identifier); + +void presence_nonce_store(peer_t *peer, uint64_t value); + +#endif /* PEERS_H */ diff --git a/src/routing.c b/src/routing.c new file mode 100644 index 0000000000000000000000000000000000000000..a743f8f2a69b0b6d01d1efd41dcd3904fc02650d --- /dev/null +++ b/src/routing.c @@ -0,0 +1,913 @@ +/* + * Coincer + * Copyright (C) 2017-2018 Coincer Developers + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#define _BSD_SOURCE /* usleep */ + +#include +#include +#include +#include +#include + +#include "autoconfig.h" +#include "crypto.h" +#include "daemon_messages.h" +#include "global_state.h" +#include "json_parser.h" +#include "linkedlist.h" +#include "log.h" +#include "neighbours.h" +#include "peers.h" +#include "routing.h" + +/** + * Simple helper to determine the destination visibility of a message. + */ +enum dest_visibility { + DV_SHOWN, /**< The message will include the destination's ID. */ + DV_HIDDEN /**< The message won't include the destination's ID. */ +}; + +static int message_send_n2n(message_t *message, neighbour_t *dest); +static int message_send_p2p(const message_t *message, + const linkedlist_t *routing_table); +static int message_send_to_neighbour(const message_t *message, + neighbour_t *dest); + +static int message_trace_store(linkedlist_t *msg_traces, + const neighbour_t *sender, + uint64_t nonce_value, + const unsigned char *from); + +static void string_broadcast(const char *string, + const linkedlist_t *neighbours, + const neighbour_t *exception); +static void string_send_to_neighbour(const char *string, + neighbour_t *dest); + +/** + * Pause the program execution for a random amount of time. + * + * @param ms_min Minimum random time in milliseconds. + * @param ms_max Maximum random time in milliseconds. Must be + * lower than 1000000. + */ +static void execution_pause_random(uint32_t ms_min, uint32_t ms_max) +{ + uint32_t pause_range_ms; + uint32_t pause_val_ms; + + pause_range_ms = get_random_uint32_t(ms_max - ms_min + 1); + pause_val_ms = ms_min + pause_range_ms; + + usleep(pause_val_ms); +} + +/** + * Broadcast a message in a neighbour to neighbour manner - replace the field + * 'from' with our neighbour-speicific pseudonym. + * + * @param message The message to be sent. The message content + * must be initialized (the type (and the data)). + * The rest is initialized in this function. + * @param neighbours Broadcast to these neighbours. + */ +static void message_broadcast_n2n(message_t *message, + const linkedlist_t *neighbours) +{ + neighbour_t *current_neigh; + linkedlist_node_t *current_node; + + current_node = linkedlist_get_first(neighbours); + while (current_node) { + current_neigh = (neighbour_t *) current_node->data; + + message_send_n2n(message, current_neigh); + + current_node = linkedlist_get_next(neighbours, current_node); + } +} + +/** + * Broadcast a message in a peer-to-peer manner (send the message as is). + * + * @param message The message to be sent. + * @param neighbours Broadcast to these neighbours. + * @param exception Don't send the message to this neighbour. + * This is typically the neighbour from whom + * we've received the message. + * + * @return 0 Success. + * @return 1 Failure. + */ +static int message_broadcast_p2p(const message_t *message, + const linkedlist_t *neighbours, + const neighbour_t *exception) +{ + char *json_message; + neighbour_t *neigh; + linkedlist_node_t *node; + + if (encode_message(message, &json_message)) { + log_debug("message_send_to_neighbour - encoding message"); + return 1; + } + + node = linkedlist_get_first(neighbours); + while (node) { + neigh = (neighbour_t *) node->data; + + if (neigh != exception) { + string_send_to_neighbour(json_message, neigh); + } + + node = linkedlist_get_next(neighbours, node); + } + + free(json_message); + return 0; +} + +/** + * Set up a message to be sent. The type (and the data) of the message + * body must be initialized before calling this function. + * + * @param message The message we want to set up. After successful + * completion of this function, this is a + * ready to be sent message. + * @param from We want to send the message under this identity. + * @param to Identifier of the destination. Can be NULL, but + * 'dest_vis' has to be set to DV_HIDDEN. + * @param dest_vis Message's destination visibility. + * + * @return 0 Successfully set up. + * @return 1 Failure. + */ +static int message_finalize(message_t *message, + identity_t *from, + const unsigned char *to, + const enum dest_visibility dest_vis) +{ + int cmp_val; + char *json_body; + uint64_t nonce_value; + + message->version = PROTOCOL_VERSION; + memcpy(message->from, + from->keypair.public_key, + crypto_box_PUBLICKEYBYTES); + + if (dest_vis == DV_HIDDEN) { + memset(message->body.to, 0x0, crypto_box_PUBLICKEYBYTES); + } else { + memcpy(message->body.to, to, crypto_box_PUBLICKEYBYTES); + } + + nonce_value = from->nonce_value + 1; + + cmp_val = memcmp(message->from, + message->body.to, + crypto_box_PUBLICKEYBYTES); + /* from ID > to ID => odd nonce; from ID < to ID => even nonce */ + if ((cmp_val > 0 && !(nonce_value & 0x01)) || + (cmp_val < 0 && (nonce_value & 0x01))) { + nonce_value++; + } + + message->body.nonce = from->nonce_value = nonce_value; + + if (encode_message_body(&message->body, &json_body)) { + return 1; + } + + sign_message(json_body, from->keypair.secret_key, message->sig); + free(json_body); + return 0; +} + +/** + * Forward someone's message on its way to destination. + * + * @param message Forward this message. + * @param json_message The message to be forwarded, just encoded. + * @param sender Neighbour who's sent us the message. + * @param global_state The global state. + * + * @return 0 Success. + * @return 1 Failure. + */ +int message_forward(const message_t *message, + const char *json_message, + const neighbour_t *sender, + global_state_t *global_state) +{ + neighbour_t *next_hop; + route_t *route; + + /* if the 'to' bytes are set to 0x0, the message is a broadcast */ + if (identifier_empty(message->body.to)) { + /* don't send the message to the one who's sent it to us */ + string_broadcast(json_message, + &global_state->neighbours, + sender); + return 0; + } + + route = route_find(&global_state->routing_table, message->body.to); + next_hop = route ? route_next_hop_get(route) : NULL; + if (!next_hop) { + return 1; + } + + message_trace_store(&global_state->message_traces, + sender, + message->body.nonce, + message->from); + + string_send_to_neighbour(json_message, next_hop); + return 0; +} + +/** + * Send a message in a neighbour-to-neighbour manner. + * + * @param message The message to be sent. + * @param dest Send the message to this neighbour. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +static int message_send_n2n(message_t *message, neighbour_t *dest) +{ + if (message_finalize(message, + &dest->my_pseudonym, + dest->pseudonym.identifier, + DV_HIDDEN)) { + return 1; + } + + return message_send_to_neighbour(message, dest); +} + +/** + * Send a p2p message towards its destination via next hop. + * + * @param message Send this p2p message. The message must contain + * destination's identifier for the next hop + * selection. + * @param routing_table The routing table. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +static int message_send_p2p(const message_t *message, + const linkedlist_t *routing_table) +{ + neighbour_t *next_hop; + route_t *route; + + route = route_find(routing_table, message->body.to); + next_hop = route ? route_next_hop_get(route) : NULL; + if (!next_hop) { + return 1; + } + + /* send the message to the next hop */ + return message_send_to_neighbour(message, next_hop); +} + +/** + * Send a message to a neighbour. + * + * @param message Send this message. + * @param dest The neighbour. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +static int message_send_to_neighbour(const message_t *message, + neighbour_t *dest) +{ + char *json_message; + + if (encode_message(message, &json_message)) { + log_debug("message_send_to_neighbour - encoding message"); + return 1; + } + + string_send_to_neighbour(json_message, dest); + + free(json_message); + return 0; +} + +/** + * A message trace staleness predicate. + * + * @param msg_trace The message trace. + * @param current_time Time to compare to. + * + * @return 1 The message trace is stale. + * @return 0 The message trace is not stale. + */ +int message_trace_is_stale(const message_trace_t *msg_trace, + const time_t *current_time) +{ + return difftime(*current_time, msg_trace->creation) >= + MESSAGE_TRACE_STALE_TIME; +} + +/** + * Store a new message trace. + * + * @param msg_traces Store to this container. + * @param sender The neighbour whose message we've forwarded. + * @param nonce_value The forwarded message's nonce value. + * @param from The originator's identifier. + * + * @return 0 Successfully stored. + * @return 1 Failure. + */ +static int message_trace_store(linkedlist_t *msg_traces, + const neighbour_t *sender, + uint64_t nonce_value, + const unsigned char *from) +{ + message_trace_t *msg_trace; + + msg_trace = (message_trace_t *) malloc(sizeof(message_trace_t)); + if (!msg_trace) { + log_error("Allocating a message trace"); + return 1; + } + + msg_trace->sender = sender; + msg_trace->nonce_value = nonce_value; + msg_trace->creation = time(NULL); + memcpy(msg_trace->from, from, crypto_box_PUBLICKEYBYTES); + + if (!linkedlist_append(msg_traces, msg_trace)) { + log_error("Storing a message trace"); + free(msg_trace); + return 1; + } + + return 0; +} + +/** + * Add a new route to the routing table. + * + * @param routing_table Add the new route in here. + * @param dest Destination peer. + * @param next_hop The neighbour who's announced the new route. + * + * @return route_t The new route. + * @return NULL Failure. + */ +route_t *route_add(linkedlist_t *routing_table, + peer_t *dest, + neighbour_t *next_hop) +{ + route_t *new_route; + + new_route = (route_t *) malloc(sizeof(route_t)); + if (!new_route) { + log_error("Creating a new route"); + return NULL; + } + + linkedlist_init(&new_route->next_hops); + if (!linkedlist_append(&new_route->next_hops, next_hop)) { + free(new_route); + log_error("Adding a new route's next hop"); + return NULL; + } + if (!(new_route->node = linkedlist_append(routing_table, new_route))) { + linkedlist_remove_all(&new_route->next_hops); + free(new_route); + log_error("Storing a new route"); + return NULL; + } + + new_route->destination = dest; + new_route->last_update = time(NULL); + + return new_route; +} + +/** + * Safely delete all route's data. + * + * @param route Delete this route's data. + */ +void route_clear(route_t *route) +{ + linkedlist_remove_all(&route->next_hops); +} + +/** + * Delete a route from a routing table. + * + * @param routing_table The routing table. + * @param dest_id The route's destination id. + */ +void route_delete(linkedlist_t *routing_table, const unsigned char *dest_id) +{ + route_t *route; + + route = route_find(routing_table, dest_id); + if (route) { + linkedlist_delete_safely(route->node, route_clear); + } +} + +/** + * Find a route in a routing table. + * + * @param routing_table The routing table. + * @param dest_id The route's destination identififer. + * + * @return route_t The requested route. + * @return NULL Route not found. + */ +route_t *route_find(const linkedlist_t *routing_table, + const unsigned char *dest_id) +{ + const unsigned char *id; + const linkedlist_node_t *node; + route_t *route; + + /* get the first node of our routing table */ + node = linkedlist_get_first(routing_table); + while (node) { + route = (route_t *) node->data; + id = route->destination->identifier; + + if (!memcmp(id, dest_id, crypto_box_PUBLICKEYBYTES)) { + return route; + } + + node = linkedlist_get_next(routing_table, node); + } + + /* destination unknown */ + return NULL; +} + +/** + * A route staleness predicate. + * + * @param route The route. + * @param current_time Time to compare to. + * + * @return 1 The route is stale. + * @return 0 The route is not stale. + */ +int route_is_stale(const route_t *route, const time_t *current_time) +{ + return difftime(*current_time, route->last_update) >= ROUTE_STALE_TIME; +} + +/** + * Add new next hop, unless it's already added. + * + * @param route Add the next hop to this route. + * @param next_hop Add this next hop. + * + * @return 0 Added or next hop has already been there. + * @return 1 Failure. + */ +int route_next_hop_add(route_t *route, neighbour_t *next_hop) +{ + if (!linkedlist_find(&route->next_hops, next_hop)) { + return linkedlist_append(&route->next_hops, next_hop) == NULL; + } + + return 0; +} + +/** + * Get a next hop for a given route. + * + * @param route The route. + * + * @return neighbour_t The next hop. + * @return NULL The route has no next hops. + */ +neighbour_t *route_next_hop_get(const route_t *route) +{ + linkedlist_node_t *next_hop_node; + + next_hop_node = route ? linkedlist_get_first(&route->next_hops) : NULL; + if (!next_hop_node) { + return NULL; + } + + return next_hop_node->data; +} + +/** + * Remove a next hop from a route, if exists. + * + * @param route Remove the next hop from this route. + * @param next_hop Remove this next hop. + */ +void route_next_hop_remove(route_t *route, neighbour_t *next_hop) +{ + linkedlist_node_t *node = linkedlist_find(&route->next_hops, next_hop); + + if (node) { + linkedlist_remove(node); + } +} + +/** + * Reset a route - remove its next hops and add a new one. + * + * @param route Reset this route. + * @param next_hop The new next hop. + * + * @return 0 Success. + * @return 1 Failure. + */ +int route_reset(route_t *route, neighbour_t *next_hop) +{ + linkedlist_remove_all(&route->next_hops); + + return linkedlist_append(&route->next_hops, next_hop) == NULL; +} + +/** + * Detect a routing loop. + * + * @param msg_traces Recent message traces. + * @param neighbour The forwarder of a new message. + * @param nonce_value The message's nonce value. + * @param from The identifier of the message originator. + * + * @return 1 A routing loop detected. + * @return 0 No routing loop detected. + */ +int routing_loop_detect(const linkedlist_t *msg_traces, + const neighbour_t *neighbour, + uint64_t nonce_value, + const unsigned char *from) +{ + const message_trace_t *msg_trace; + const linkedlist_node_t *node; + + node = linkedlist_get_first(msg_traces); + while (node) { + msg_trace = (message_trace_t *) node->data; + + /* the same message but different neighbour => routing loop */ + if (msg_trace->nonce_value == nonce_value && + msg_trace->sender != neighbour && + !memcmp(msg_trace->from, from, crypto_box_PUBLICKEYBYTES)) { + return 1; + } + + node = linkedlist_get_next(msg_traces, node); + } + + return 0; +} + +/** + * Remove a routing loop from a route given by its destination's identifier. + * Send p2p.route.sol if we've removed our last next hop. + * + * @param routing_table The routing table. + * @param neighbours Our neighbours. + * @param identities Our identities. + * @param dest_id The identifier of the destination of the route + * that's caused a routing loop. + */ +void routing_loop_remove(linkedlist_t *routing_table, + linkedlist_t *neighbours, + linkedlist_t *identities, + const unsigned char *dest_id) +{ + identity_t *identity; + neighbour_t *next_hop; + route_t *route; + + route = route_find(routing_table, dest_id); + + /* get the next hop or NULL if the message destination is unknown + * or if we have no next hop to reach the destination */ + next_hop = route ? route_next_hop_get(route) : NULL; + if (next_hop) { + route_next_hop_remove(route, next_hop); + + next_hop = route_next_hop_get(route); + if (!next_hop) { + /* create a temporary identity for the p2p.route.sol */ + identity = identity_generate(IDENTITY_TMP); + /* if appending succeeded */ + if (linkedlist_append(identities, identity)) { + /* if successfully sent */ + if (!send_p2p_route_sol(neighbours, + identity, + dest_id)) { + return; + } + } + free(identity); + route_delete(routing_table, dest_id); + } + } +} + +/** + * Remove a next hop from all routes. + * + * @param routing_table The routing table. + * @param next_hop Remove this next hop. + */ +void routing_table_remove_next_hop(linkedlist_t *routing_table, + neighbour_t *next_hop) +{ + const linkedlist_node_t *node; + route_t *route; + + node = linkedlist_get_first(routing_table); + while (node) { + route = (route_t *) node->data; + + route_next_hop_remove(route, next_hop); + + node = linkedlist_get_next(routing_table, node); + } +} + +/** + * Send p2p.bye message. + * + * @param neighbours Our neighbours. + * @param identity Announce departure of this identity. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_bye(linkedlist_t *neighbours, identity_t *identity) +{ + message_t p2p_bye_msg; + int ret; + + if (create_p2p_bye(&p2p_bye_msg)) { + return 1; + } + if (!(ret = message_finalize(&p2p_bye_msg, + identity, + NULL, + DV_HIDDEN))) { + /* route as P2P (don't use our neighbour pseudonym) */ + ret = message_broadcast_p2p(&p2p_bye_msg, + neighbours, + NULL); + } + + message_delete(&p2p_bye_msg); + return ret; +} + +/** + * Send p2p.hello message. + * + * @param dest Send the message to this neighbour. + * @param port Our listening port. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_hello(neighbour_t *dest, unsigned short port) +{ + message_t p2p_hello_msg; + int ret; + + if (create_p2p_hello(&p2p_hello_msg, port)) { + return 1; + } + ret = message_send_n2n(&p2p_hello_msg, dest); + + message_delete(&p2p_hello_msg); + return ret; +} + +/** + * Send p2p.peers.adv message. + * + * @param dest Send the message to this neighbour. + * @param hosts Hosts known to us. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_peers_adv(neighbour_t *dest, const linkedlist_t *hosts) +{ + message_t p2p_peers_adv_msg; + int ret; + + if (create_p2p_peers_adv(&p2p_peers_adv_msg, hosts)) { + return 1; + } + ret = message_send_n2n(&p2p_peers_adv_msg, dest); + + message_delete(&p2p_peers_adv_msg); + return ret; +} + +/** + * Send p2p.peers.sol message. + * + * @param dest Send the message to this neighbour. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_peers_sol(neighbour_t *dest) +{ + message_t p2p_peers_sol_msg; + int ret; + + if (create_p2p_peers_sol(&p2p_peers_sol_msg)) { + return 1; + } + if (!(ret = message_send_n2n(&p2p_peers_sol_msg, dest))) { + set_neighbour_flags(dest, NEIGHBOUR_ADDRS_REQ); + } + + message_delete(&p2p_peers_sol_msg); + return ret; +} + +/** + * Send p2p.ping message. + * + * @param dest Send the message to this neighbour. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_ping(neighbour_t *dest) +{ + message_t p2p_ping_msg; + int ret; + + if (create_p2p_ping(&p2p_ping_msg)) { + return 1; + } + ret = message_send_n2n(&p2p_ping_msg, dest); + + message_delete(&p2p_ping_msg); + return ret; +} + +/** + * Send p2p.pong message. + * + * @param dest Send the message to this neighbour. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_pong(neighbour_t *dest) +{ + message_t p2p_pong_msg; + int ret; + + if (create_p2p_pong(&p2p_pong_msg)) { + return 1; + } + ret = message_send_n2n(&p2p_pong_msg, dest); + + message_delete(&p2p_pong_msg); + return ret; +} + +/** + * Send p2p.route.adv message. + * + * @param neighbours Our neighbours. + * @param identity Advertise this identity. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_route_adv(linkedlist_t *neighbours, identity_t *identity) +{ + message_t p2p_route_adv_msg; + int ret; + + if (create_p2p_route_adv(&p2p_route_adv_msg)) { + return 1; + } + if (!(ret = message_finalize(&p2p_route_adv_msg, + identity, + NULL, + DV_HIDDEN))) { + /* timing attack protection */ + execution_pause_random(250, 2000); + + /* route as P2P (don't use our neighbour pseudonym) */ + if (!(ret = message_broadcast_p2p(&p2p_route_adv_msg, + neighbours, + NULL))) { + identity->last_adv = time(NULL); + } + } + + message_delete(&p2p_route_adv_msg); + return ret; +} + +/** + * Send p2p.route.sol message. + * + * @param neighbours Our neighbours. + * @param identity Make a solicitation under this identity. + * @param target Destination peer's identifier. + * + * @return 0 Successfully sent. + * @return 1 Failure. + */ +int send_p2p_route_sol(linkedlist_t *neighbours, + identity_t *identity, + const unsigned char *target) +{ + message_t p2p_route_sol_msg; + int ret; + + if (create_p2p_route_sol(&p2p_route_sol_msg, target)) { + return 1; + } + if (!(ret = message_finalize(&p2p_route_sol_msg, + identity, + NULL, + DV_HIDDEN))) { + /* route as P2P (don't use our neighbour pseudonym) */ + ret = message_broadcast_p2p(&p2p_route_sol_msg, + neighbours, + NULL); + } + + message_delete(&p2p_route_sol_msg); + return ret; +} + +/** + * Broadcast a text (json message) to our neighbour. + * + * @param string Send this text. + * @param neighbours Our neighbours. + * @param exception Don't send the message to this neighbour. + * This is typically the neighbour from whom + * we've received the message. + */ +static void string_broadcast(const char *string, + const linkedlist_t *neighbours, + const neighbour_t *exception) +{ + linkedlist_node_t *node; + neighbour_t *neighbour; + + node = linkedlist_get_first(neighbours); + while (node) { + neighbour = (neighbour_t *) node->data; + if (neighbour != exception) { + string_send_to_neighbour(string, neighbour); + } + node = linkedlist_get_next(neighbours, node); + } +} + +/** + * Send a text (json message) to a neighbour. + * + * @param string Send this text. + * @param dest To this neighbour. + */ +static void string_send_to_neighbour(const char *string, neighbour_t *dest) +{ + struct evbuffer *output; + + output = bufferevent_get_output(dest->buffer_event); + evbuffer_add_printf(output, "%s", string); +} diff --git a/src/routing.h b/src/routing.h new file mode 100644 index 0000000000000000000000000000000000000000..e5da6172a3174e72fbcef44cc6a033589ded6c4c --- /dev/null +++ b/src/routing.h @@ -0,0 +1,107 @@ +/* + * Coincer + * Copyright (C) 2017-2018 Coincer Developers + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#ifndef ROUTING_H +#define ROUTING_H + +#include +#include +#include + +#include "daemon_messages.h" +#include "global_state.h" +#include "linkedlist.h" +#include "neighbours.h" +#include "peers.h" + +/** After this many seconds we consider a message trace to be stale. */ +#define MESSAGE_TRACE_STALE_TIME 60 +/** After this many seconds we consider a route to be stale. */ +#define ROUTE_STALE_TIME 180 + +/** A message trace representation. */ +typedef struct s_message_trace { + /** Message's nonce value. */ + uint64_t nonce_value; + /** Identifier of the sender. */ + char from[crypto_box_PUBLICKEYBYTES]; + /** Neighbour who's sent us the message */ + const neighbour_t *sender; + /** Creation timestamp. */ + time_t creation; +} message_trace_t; + +/** Data type for a routing table. */ +typedef struct s_route { + /** Destination peer. */ + peer_t *destination; + /** List of possible next hops (pointers to our neighbours) + * to the destination. Naturally sorted by delay viability. */ + linkedlist_t next_hops; + /** The time of the last destination's announcement of presence or + * the time of this route's creation. */ + time_t last_update; + /** The route's node in the routing table. */ + linkedlist_node_t *node; +} route_t; + +int message_forward(const message_t *message, + const char *json_message, + const neighbour_t *sender, + global_state_t *global_state); + +int message_trace_is_stale(const message_trace_t *msg_trace, + const time_t *current_time); + +route_t *route_add(linkedlist_t *routing_table, + peer_t *dest, + neighbour_t *next_hop); +void route_clear(route_t *route); +void route_delete(linkedlist_t *routing_table, const unsigned char *dest_id); +route_t *route_find(const linkedlist_t *routing_table, + const unsigned char *dest_id); +int route_is_stale(const route_t *route, const time_t *current_time); +int route_next_hop_add(route_t *route, neighbour_t *next_hop); +neighbour_t *route_next_hop_get(const route_t *route); +void route_next_hop_remove(route_t *route, neighbour_t *next_hop); +int route_reset(route_t *route, neighbour_t *next_hop); + +int routing_loop_detect(const linkedlist_t *msg_traces, + const neighbour_t *neighbour, + uint64_t nonce_value, + const unsigned char *from); +void routing_loop_remove(linkedlist_t *routing_table, + linkedlist_t *neighbours, + linkedlist_t *identities, + const unsigned char *dest_id); + +void routing_table_remove_next_hop(linkedlist_t *routing_table, + neighbour_t *next_hop); + +int send_p2p_bye(linkedlist_t *neighbours, identity_t *identity); +int send_p2p_hello(neighbour_t *dest, unsigned short port); +int send_p2p_peers_adv(neighbour_t *dest, const linkedlist_t *hosts); +int send_p2p_peers_sol(neighbour_t *dest); +int send_p2p_ping(neighbour_t *dest); +int send_p2p_pong(neighbour_t *dest); +int send_p2p_route_adv(linkedlist_t *neighbours, identity_t *identity); +int send_p2p_route_sol(linkedlist_t *neighbours, + identity_t *identity, + const unsigned char *target); + +#endif /* ROUTING_H */