From 04543a57e73e29c3e2a1968fd330f03c94dd6059 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Fri, 5 Jan 2018 22:49:30 +0100 Subject: [PATCH] Add a cache of recently seen addresses. This maintains a cache file for each host we have communicated with, either via TCP or UDP. The cache is used when trying to make outgoing connections, and is updated whenever a successful TCP or UDP connection is established. Up to 8 addresses are stored in the cache. Currently, the cache is stored in /etc/tinc/NETNAME/cache. The directory has to be manually created to opt in to this feature for now. --- src/Makefile.am | 1 + src/address_cache.c | 263 ++++++++++++++++++++++++++++++++++++++++++++ src/address_cache.h | 50 +++++++++ src/autoconnect.c | 15 +-- src/net.h | 8 +- src/net_packet.c | 9 +- src/net_socket.c | 152 +++++-------------------- src/node.c | 5 + src/node.h | 2 + src/protocol_misc.c | 10 +- 10 files changed, 367 insertions(+), 148 deletions(-) create mode 100644 src/address_cache.c create mode 100644 src/address_cache.h diff --git a/src/Makefile.am b/src/Makefile.am index 253e02a5..ebf8f71e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -42,6 +42,7 @@ chacha_poly1305_SOURCES = \ chacha-poly1305/poly1305.c chacha-poly1305/poly1305.h tincd_SOURCES = \ + address_cache.c address_cache.h \ autoconnect.c autoconnect.h \ buffer.c buffer.h \ cipher.h \ diff --git a/src/address_cache.c b/src/address_cache.c new file mode 100644 index 00000000..552e1f28 --- /dev/null +++ b/src/address_cache.c @@ -0,0 +1,263 @@ +/* + address_cache.c -- Manage cache of recently seen addresses + Copyright (C) 2018 Guus Sliepen + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "address_cache.h" +#include "conf.h" +#include "names.h" +#include "netutl.h" +#include "xalloc.h" + +static const unsigned int NOT_CACHED = -1; + +// Find edges pointing to this node, and use them to build a list of unique, known addresses. +static struct addrinfo *get_known_addresses(node_t *n) { + struct addrinfo *ai = NULL; + struct addrinfo *oai = NULL; + + for splay_each(edge_t, e, n->edge_tree) { + if(!e->reverse) { + continue; + } + + bool found = false; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + if(!sockaddrcmp(&e->reverse->address, (sockaddr_t *)aip->ai_addr)) { + found = true; + break; + } + } + + if(found) { + continue; + } + + oai = ai; + ai = xzalloc(sizeof(*ai)); + ai->ai_family = e->reverse->address.sa.sa_family; + ai->ai_socktype = SOCK_STREAM; + ai->ai_protocol = IPPROTO_TCP; + ai->ai_addrlen = SALEN(e->reverse->address.sa); + ai->ai_addr = xmalloc(ai->ai_addrlen); + memcpy(ai->ai_addr, &e->reverse->address, ai->ai_addrlen); + ai->ai_next = oai; + } + + return ai; +} + +static void free_known_addresses(struct addrinfo *ai) { + for(struct addrinfo *aip = ai, *next; aip; aip = next) { + next = aip->ai_next; + free(aip); + } +} + +static unsigned int find_cached(address_cache_t *cache, const sockaddr_t *sa) { + for(unsigned int i = 0; i < cache->data.used; i++) + if(!sockaddrcmp(&cache->data.address[i], sa)) { + return i; + } + + return NOT_CACHED; +} + +void add_recent_address(address_cache_t *cache, const sockaddr_t *sa) { + // Check if it's already cached + unsigned int pos = find_cached(cache, sa); + + // It's in the first spot, so nothing to do + if (pos == 0) { + return; + } + + // Shift everything, move/add the address to the first slot + if(pos == NOT_CACHED) { + if(cache->data.used < MAX_CACHED_ADDRESSES) + cache->data.used++; + pos = cache->data.used - 1; + } + + memmove(&cache->data.address[1], &cache->data.address[0], pos * sizeof(cache->data.address[0])); + + cache->data.address[0] = *sa; + + // Write the cache + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "cache" SLASH "%s", confbase, cache->node->name); + FILE *fp = fopen(fname, "wb"); + + if(fp) { + fprintf(stderr, "Writing cache to %s\n", fname); + fwrite(&cache->data, sizeof(cache->data), 1, fp); + fclose(fp); + } +} + +const sockaddr_t *get_recent_address(address_cache_t *cache) { + // Check if there is an address in our cache of recently seen addresses + if(cache->tried < cache->data.used) { + return &cache->data.address[cache->tried++]; + } + + // Next, check any recently seen addresses not in our cache + while(cache->tried == cache->data.used) { + if(!cache->ai) { + cache->aip = cache->ai = get_known_addresses(cache->node); + } + + if(cache->ai) { + if(cache->aip) { + sockaddr_t *sa = (sockaddr_t *)cache->aip; + + if(find_cached(cache, sa) != NOT_CACHED) { + continue; + } + + cache->aip = cache->aip->ai_next; + return sa; + } else { + free_known_addresses(cache->ai); + cache->ai = NULL; + } + } + + cache->tried++; + } + + // Otherwise, check if there are any known Address statements + if(!cache->config_tree) { + init_configuration(&cache->config_tree); + read_host_config(cache->config_tree, cache->node->name, false); + cache->cfg = lookup_config(cache->config_tree, "Address"); + } + + while(cache->cfg && !cache->ai) { + char *address, *port; + + get_config_string(cache->cfg, &address); + + char *space = strchr(address, ' '); + + if(space) { + port = xstrdup(space + 1); + *space = 0; + } else { + if(!get_config_string(lookup_config(cache->config_tree, "Port"), &port)) { + port = xstrdup("655"); + } + } + + cache->aip = cache->ai = str2addrinfo(address, port, SOCK_STREAM); + free(address); + free(port); + + cache->cfg = lookup_config_next(cache->config_tree, cache->cfg); + } + + if(cache->aip) { + sockaddr_t *sa = (sockaddr_t *)cache->aip->ai_addr; + cache->aip = cache->aip->ai_next; + + if(!cache->aip) { + freeaddrinfo(cache->aip); + cache->aip = NULL; + } + + return sa; + } + + // We're all out of addresses. + exit_configuration(&cache->config_tree); + return false; +} + +address_cache_t *open_address_cache(node_t *node) { + address_cache_t *cache = xmalloc(sizeof(*cache)); + cache->node = node; + + // Try to open an existing address cache + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "cache" SLASH "%s", confbase, node->name); + FILE *fp = fopen(fname, "rb"); + + if(!fp || fread(&cache->data, sizeof(cache->data), 1, fp) != 1 || cache->data.version != ADDRESS_CACHE_VERSION) { + memset(&cache->data, 0, sizeof(cache->data)); + } + + if(fp) { + fclose(fp); + } + + // Ensure we have a valid state + cache->config_tree = NULL; + cache->cfg = NULL; + cache->ai = NULL; + cache->aip = NULL; + cache->tried = 0; + cache->data.version = ADDRESS_CACHE_VERSION; + + if(cache->data.used > MAX_CACHED_ADDRESSES) { + cache->data.used = 0; + } + + return cache; +} + +void reset_address_cache(address_cache_t *cache, const sockaddr_t *sa) { + if(sa) { + add_recent_address(cache, sa); + } + + if(cache->config_tree) { + exit_configuration(&cache->config_tree); + } + + if(cache->ai) { + if(cache->tried == cache->data.used) { + free_known_addresses(cache->ai); + } else { + freeaddrinfo(cache->ai); + } + } + + cache->config_tree = NULL; + cache->cfg = NULL; + cache->ai = NULL; + cache->aip = NULL; + cache->tried = 0; +} + +void close_address_cache(address_cache_t *cache) { + if(cache->config_tree) { + exit_configuration(&cache->config_tree); + } + + if(cache->ai) { + if(cache->tried == cache->data.used) { + free_known_addresses(cache->ai); + } else { + freeaddrinfo(cache->ai); + } + } + + free(cache); +} diff --git a/src/address_cache.h b/src/address_cache.h new file mode 100644 index 00000000..4ce6ec5e --- /dev/null +++ b/src/address_cache.h @@ -0,0 +1,50 @@ +#ifndef TINC_ADDRESS_CACHE_H +#define TINC_ADDRESS_CACHE_H + +/* + address_cache.h -- header for address_cache.c + Copyright (C) 2018 Guus Sliepen + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "net.h" + +#define MAX_CACHED_ADDRESSES 8 +#define ADDRESS_CACHE_VERSION 1 + +typedef struct address_cache_t { + struct node_t *node; + struct splay_tree_t *config_tree; + struct config_t *cfg; + struct addrinfo *ai; + struct addrinfo *aip; + unsigned int tried; + + struct { + unsigned int version; + unsigned int used; + sockaddr_t address[MAX_CACHED_ADDRESSES]; + } data; +} address_cache_t; + +void add_recent_address(address_cache_t *cache, const sockaddr_t *sa); +const sockaddr_t *get_recent_address(address_cache_t *cache); + +address_cache_t *open_address_cache(struct node_t *node); +void reset_address_cache(address_cache_t *cache, const sockaddr_t *sa); +void close_address_cache(address_cache_t *cache); + +#endif diff --git a/src/autoconnect.c b/src/autoconnect.c index 132467e6..0fa6f4e5 100644 --- a/src/autoconnect.c +++ b/src/autoconnect.c @@ -54,7 +54,7 @@ static void make_new_connection() { bool found = false; for list_each(outgoing_t, outgoing, outgoing_list) { - if(!strcmp(outgoing->name, n->name)) { + if(outgoing->node == n) { found = true; break; } @@ -63,7 +63,7 @@ static void make_new_connection() { if(!found) { logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name); outgoing_t *outgoing = xzalloc(sizeof(*outgoing)); - outgoing->name = xstrdup(n->name); + outgoing->node = n; list_insert_tail(outgoing_list, outgoing); setup_outgoing_connection(outgoing, false); } @@ -92,15 +92,16 @@ static void connect_to_unreachable() { return; } - /* Are we already trying to make an outgoing connection to it? If not, return. */ - for list_each(outgoing_t, outgoing, outgoing_list) - if(!strcmp(outgoing->name, n->name)) { + /* Are we already trying to make an outgoing connection to it? If so, return. */ + for list_each(outgoing_t, outgoing, outgoing_list) { + if(outgoing->node == n) { return; } + } logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name); outgoing_t *outgoing = xzalloc(sizeof(*outgoing)); - outgoing->name = xstrdup(n->name); + outgoing->node = n; list_insert_tail(outgoing_list, outgoing); setup_outgoing_connection(outgoing, false); @@ -159,7 +160,7 @@ static void drop_superfluous_pending_connections() { continue; } - logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->name); + logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->node->name); list_delete_node(outgoing_list, node); } } diff --git a/src/net.h b/src/net.h index a4346445..827194e7 100644 --- a/src/net.h +++ b/src/net.h @@ -122,13 +122,9 @@ typedef struct listen_socket_t { #include "list.h" typedef struct outgoing_t { - char *name; + struct node_t *node; int timeout; - splay_tree_t *config_tree; - struct config_t *cfg; - struct addrinfo *ai; // addresses from config files - struct addrinfo *aip; - struct addrinfo *kai; // addresses known via other online nodes (use free_known_addresses()) + struct address_cache_t *address_cache; timeout_t ev; } outgoing_t; diff --git a/src/net_packet.c b/src/net_packet.c index 21751806..7bd619fe 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -30,6 +30,7 @@ #include LZO1X_H #endif +#include "address_cache.h" #include "cipher.h" #include "conf.h" #include "connection.h" @@ -153,7 +154,13 @@ static void udp_probe_h(node_t *n, vpn_packet_t *packet, length_t len) { /* It's a valid reply: now we know bidirectional communication is possible using the address and socket that the reply packet used. */ - n->status.udp_confirmed = true; + if(!n->status.udp_confirmed) { + n->status.udp_confirmed = true; + fprintf(stderr, "Updating address cache...\n"); + if (!n->address_cache) + n->address_cache = open_address_cache(n); + reset_address_cache(n->address_cache, &n->address); + } // Reset the UDP ping timer. n->udp_ping_sent = now; diff --git a/src/net_socket.c b/src/net_socket.c index 979e84b9..30ab79e2 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -22,6 +22,7 @@ #include "system.h" +#include "address_cache.h" #include "conf.h" #include "connection.h" #include "control_common.h" @@ -491,73 +492,26 @@ static void handle_meta_io(void *data, int flags) { } } -static void free_known_addresses(struct addrinfo *ai) { - for(struct addrinfo *aip = ai, *next; aip; aip = next) { - next = aip->ai_next; - free(aip); - } -} - bool do_outgoing_connection(outgoing_t *outgoing) { - char *address, *port, *space; + const sockaddr_t *sa; struct addrinfo *proxyai = NULL; int result; begin: + sa = get_recent_address(outgoing->address_cache); - if(!outgoing->ai && !outgoing->kai) { - if(!outgoing->cfg) { - logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s", outgoing->name); - retry_outgoing(outgoing); - return false; - } - - get_config_string(outgoing->cfg, &address); - - space = strchr(address, ' '); - - if(space) { - port = xstrdup(space + 1); - *space = 0; - } else { - if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) { - port = xstrdup("655"); - } - } - - outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); - free(address); - free(port); - - outgoing->aip = outgoing->ai; - outgoing->cfg = lookup_config_next(outgoing->config_tree, outgoing->cfg); - } - - if(!outgoing->aip) { - if(outgoing->ai) { - freeaddrinfo(outgoing->ai); - } - - outgoing->ai = NULL; - - if(outgoing->kai) { - free_known_addresses(outgoing->kai); - } - - outgoing->kai = NULL; - - goto begin; + if(!sa) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s", outgoing->node->name); + retry_outgoing(outgoing); + return false; } connection_t *c = new_connection(); c->outgoing = outgoing; - - memcpy(&c->address, outgoing->aip->ai_addr, outgoing->aip->ai_addrlen); - outgoing->aip = outgoing->aip->ai_next; - + c->address = *sa; c->hostname = sockaddr2hostname(&c->address); - logger(DEBUG_CONNECTIONS, LOG_INFO, "Trying to connect to %s (%s)", outgoing->name, c->hostname); + logger(DEBUG_CONNECTIONS, LOG_INFO, "Trying to connect to %s (%s)", outgoing->node->name, c->hostname); if(!proxytype) { c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); @@ -617,7 +571,7 @@ begin: } if(result == -1 && !sockinprogress(sockerrno)) { - logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not connect to %s (%s): %s", outgoing->name, c->hostname, sockstrerror(sockerrno)); + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not connect to %s (%s): %s", outgoing->node->name, c->hostname, sockstrerror(sockerrno)); free_connection(c); goto begin; @@ -627,7 +581,7 @@ begin: c->last_ping_time = time(NULL); c->status.connecting = true; - c->name = xstrdup(outgoing->name); + c->name = xstrdup(outgoing->node->name); #ifndef DISABLE_LEGACY c->outcipher = myself->connection->outcipher; c->outdigest = myself->connection->outdigest; @@ -643,50 +597,13 @@ begin: return true; } -// Find edges pointing to this node, and use them to build a list of unique, known addresses. -static struct addrinfo *get_known_addresses(node_t *n) { - struct addrinfo *ai = NULL; - struct addrinfo *oai = NULL; - - for splay_each(edge_t, e, n->edge_tree) { - if(!e->reverse) { - continue; - } - - bool found = false; - - for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { - if(!sockaddrcmp(&e->reverse->address, (sockaddr_t *)aip->ai_addr)) { - found = true; - break; - } - } - - if(found) { - continue; - } - - oai = ai; - ai = xzalloc(sizeof(*ai)); - ai->ai_family = e->reverse->address.sa.sa_family; - ai->ai_socktype = SOCK_STREAM; - ai->ai_protocol = IPPROTO_TCP; - ai->ai_addrlen = SALEN(e->reverse->address.sa); - ai->ai_addr = xmalloc(ai->ai_addrlen); - memcpy(ai->ai_addr, &e->reverse->address, ai->ai_addrlen); - ai->ai_next = oai; - } - - return ai; -} - void setup_outgoing_connection(outgoing_t *outgoing, bool verbose) { timeout_del(&outgoing->ev); - node_t *n = lookup_node(outgoing->name); + node_t *n = outgoing->node; - if(n && n->connection) { - logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", outgoing->name); + if(n->connection) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", n->name); if(!n->connection->outgoing) { n->connection->outgoing = outgoing; @@ -696,19 +613,8 @@ void setup_outgoing_connection(outgoing_t *outgoing, bool verbose) { } } - init_configuration(&outgoing->config_tree); - read_host_config(outgoing->config_tree, outgoing->name, verbose); - outgoing->cfg = lookup_config(outgoing->config_tree, "Address"); - - if(!outgoing->cfg) { - if(n) { - outgoing->aip = outgoing->kai = get_known_addresses(n); - } - - if(!outgoing->kai) { - logger(verbose ? DEBUG_ALWAYS : DEBUG_CONNECTIONS, LOG_DEBUG, "No address known for %s", outgoing->name); - goto remove; - } + if(!outgoing->address_cache) { + outgoing->address_cache = open_address_cache(n); } do_outgoing_connection(outgoing); @@ -859,20 +765,8 @@ void handle_new_unix_connection(void *data, int flags) { static void free_outgoing(outgoing_t *outgoing) { timeout_del(&outgoing->ev); - if(outgoing->ai) { - freeaddrinfo(outgoing->ai); - } - - if(outgoing->kai) { - free_known_addresses(outgoing->kai); - } - - if(outgoing->config_tree) { - exit_configuration(&outgoing->config_tree); - } - - if(outgoing->name) { - free(outgoing->name); + if(outgoing->address_cache) { + close_address_cache(outgoing->address_cache); } free(outgoing); @@ -911,7 +805,7 @@ void try_outgoing_connections(void) { bool found = false; for list_each(outgoing_t, outgoing, outgoing_list) { - if(!strcmp(outgoing->name, name)) { + if(!strcmp(outgoing->node->name, name)) { found = true; outgoing->timeout = 0; break; @@ -920,7 +814,13 @@ void try_outgoing_connections(void) { if(!found) { outgoing_t *outgoing = xzalloc(sizeof(*outgoing)); - outgoing->name = name; + node_t *n = lookup_node(name); + if(!n) { + n = new_node(); + n->name = xstrdup(name); + node_add(n); + } + outgoing->node = n; list_insert_tail(outgoing_list, outgoing); setup_outgoing_connection(outgoing, true); } diff --git a/src/node.c b/src/node.c index c00124d5..bea23c7b 100644 --- a/src/node.c +++ b/src/node.c @@ -20,6 +20,7 @@ #include "system.h" +#include "address_cache.h" #include "control_common.h" #include "hash.h" #include "logger.h" @@ -118,6 +119,10 @@ void free_node(node_t *n) { free(n->late); } + if(n->address_cache) { + close_address_cache(n->address_cache); + } + free(n); } diff --git a/src/node.h b/src/node.h index daa0830a..496880f4 100644 --- a/src/node.h +++ b/src/node.h @@ -110,6 +110,8 @@ typedef struct node_t { uint64_t in_bytes; uint64_t out_packets; uint64_t out_bytes; + + struct address_cache_t *address_cache; } node_t; extern struct node_t *myself; diff --git a/src/protocol_misc.c b/src/protocol_misc.c index d4a45e8c..d808d73f 100644 --- a/src/protocol_misc.c +++ b/src/protocol_misc.c @@ -20,6 +20,7 @@ #include "system.h" +#include "address_cache.h" #include "conf.h" #include "connection.h" #include "logger.h" @@ -68,14 +69,7 @@ bool pong_h(connection_t *c, const char *request) { if(c->outgoing) { c->outgoing->timeout = 0; - c->outgoing->cfg = NULL; - - if(c->outgoing->ai) { - freeaddrinfo(c->outgoing->ai); - } - - c->outgoing->ai = NULL; - c->outgoing->aip = NULL; + reset_address_cache(c->outgoing->address_cache, &c->address); } return true; -- 2.20.1