281 lines
6.7 KiB
C
281 lines
6.7 KiB
C
/*
|
|
address_cache.c -- Manage cache of recently seen addresses
|
|
Copyright (C) 2018 Guus Sliepen <guus@tinc-vpn.org>
|
|
|
|
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) {
|
|
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->ai_addr;
|
|
cache->aip = cache->aip->ai_next;
|
|
|
|
if(find_cached(cache, sa) != NOT_CACHED) {
|
|
continue;
|
|
}
|
|
|
|
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->aip) {
|
|
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");
|
|
}
|
|
}
|
|
|
|
if(cache->ai) {
|
|
free_known_addresses(cache->ai);
|
|
}
|
|
|
|
cache->aip = cache->ai = str2addrinfo(address, port, SOCK_STREAM);
|
|
|
|
if(cache->ai) {
|
|
struct addrinfo *ai = NULL;
|
|
|
|
for(; cache->aip; cache->aip = cache->aip->ai_next) {
|
|
struct addrinfo *oai = ai;
|
|
|
|
ai = xzalloc(sizeof(*ai));
|
|
ai->ai_family = cache->aip->ai_family;
|
|
ai->ai_socktype = cache->aip->ai_socktype;
|
|
ai->ai_protocol = cache->aip->ai_protocol;
|
|
ai->ai_addrlen = cache->aip->ai_addrlen;
|
|
ai->ai_addr = xmalloc(ai->ai_addrlen);
|
|
memcpy(ai->ai_addr, cache->aip->ai_addr, ai->ai_addrlen);
|
|
ai->ai_next = oai;
|
|
}
|
|
|
|
freeaddrinfo(cache->ai);
|
|
cache->aip = cache->ai = ai;
|
|
}
|
|
|
|
free(address);
|
|
free(port);
|
|
|
|
cache->cfg = lookup_config_next(cache->config_tree, cache->cfg);
|
|
}
|
|
|
|
if(cache->ai) {
|
|
if(cache->aip) {
|
|
sockaddr_t *sa = (sockaddr_t *)cache->aip->ai_addr;
|
|
|
|
cache->aip = cache->aip->ai_next;
|
|
return sa;
|
|
} else {
|
|
free_known_addresses(cache->ai);
|
|
cache->ai = NULL;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
free_known_addresses(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) {
|
|
free_known_addresses(cache->ai);
|
|
}
|
|
|
|
free(cache);
|
|
}
|