tinc/src/node.c
Etienne Dechamps 98716a227e Move PMTU discovery code into the TX path.
Currently, the PMTU discovery code is run by a timeout callback,
independently of tunnel activity. This commit moves it into the TX
path, meaning that send_mtu_probe_handler() is only called if a
packet is about to be sent. Consequently, it has been renamed to
try_mtu() for consistency with try_tx(), try_udp() and try_sptps().

Running PMTU discovery code only as part of the TX path prevents
PMTU discovery from generating unreasonable amounts of traffic when
the "real" traffic is negligible. One extreme example is sending one
real packet and then going silent: in the current code this one little
packet will result in the entire PMTU discovery algorithm being run
from start to finish, resulting in absurd write traffic amplification.
With this patch, PMTU discovery stops as soon as "real" packets stop
flowing, and will be no more aggressive than the underlying traffic.

Furthermore, try_mtu() only runs if there is confirmed UDP
connectivity as per the UDP discovery mechanism. This prevents
unnecessary network chatter - previously, the PMTU discovery code
would send bursts of (potentially large) probe packets every second
even if there was nothing on the other side. With this patch, the
PMTU code only does that if something replied to the lightweight UDP
discovery pings.

These inefficiencies were made even worse when the node is not a
direct neighbour, as tinc will use PMTU discovery both on the
destination node *and* the relay. UDP discovery is more lightweight for
this purpose.

As a bonus, this code simplifies overall code somewhat - state is
easier to manage when code is run in predictable contexts as opposed
to "surprise callbacks". In addition, there is no need to call PMTU
discovery code outside of net_packet.c anymore, thereby simplifying
module boundaries.
2015-01-01 17:40:15 +00:00

214 lines
5.4 KiB
C

/*
node.c -- node tree management
Copyright (C) 2001-2013 Guus Sliepen <guus@tinc-vpn.org>,
2001-2005 Ivo Timmermans
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 "control_common.h"
#include "hash.h"
#include "logger.h"
#include "net.h"
#include "netutl.h"
#include "node.h"
#include "splay_tree.h"
#include "utils.h"
#include "xalloc.h"
#include "ed25519/sha512.h"
splay_tree_t *node_tree;
static splay_tree_t *node_id_tree;
static hash_t *node_udp_cache;
static hash_t *node_id_cache;
node_t *myself;
static int node_compare(const node_t *a, const node_t *b) {
return strcmp(a->name, b->name);
}
static int node_id_compare(const node_t *a, const node_t *b) {
return memcmp(&a->id, &b->id, sizeof(node_id_t));
}
void init_nodes(void) {
node_tree = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node);
node_id_tree = splay_alloc_tree((splay_compare_t) node_id_compare, NULL);
node_udp_cache = hash_alloc(0x100, sizeof(sockaddr_t));
node_id_cache = hash_alloc(0x100, sizeof(node_id_t));
}
void exit_nodes(void) {
hash_free(node_id_cache);
hash_free(node_udp_cache);
splay_delete_tree(node_id_tree);
splay_delete_tree(node_tree);
}
node_t *new_node(void) {
node_t *n = xzalloc(sizeof *n);
if(replaywin) n->late = xzalloc(replaywin);
n->subnet_tree = new_subnet_tree();
n->edge_tree = new_edge_tree();
n->mtu = MTU;
n->maxmtu = MTU;
return n;
}
void free_node(node_t *n) {
if(n->subnet_tree)
free_subnet_tree(n->subnet_tree);
if(n->edge_tree)
free_edge_tree(n->edge_tree);
sockaddrfree(&n->address);
#ifndef DISABLE_LEGACY
cipher_close(n->incipher);
digest_close(n->indigest);
cipher_close(n->outcipher);
digest_close(n->outdigest);
#endif
ecdsa_free(n->ecdsa);
sptps_stop(&n->sptps);
timeout_del(&n->udp_ping_timeout);
if(n->hostname)
free(n->hostname);
if(n->name)
free(n->name);
if(n->late)
free(n->late);
free(n);
}
void node_add(node_t *n) {
unsigned char buf[64];
sha512(n->name, strlen(n->name),buf);
memcpy(&n->id, buf, sizeof n->id);
splay_insert(node_tree, n);
splay_insert(node_id_tree, n);
}
void node_del(node_t *n) {
hash_delete(node_udp_cache, &n->address);
hash_delete(node_id_cache, &n->id);
for splay_each(subnet_t, s, n->subnet_tree)
subnet_del(n, s);
for splay_each(edge_t, e, n->edge_tree)
edge_del(e);
splay_delete(node_id_tree, n);
splay_delete(node_tree, n);
}
node_t *lookup_node(char *name) {
node_t n = {NULL};
n.name = name;
return splay_search(node_tree, &n);
}
node_t *lookup_node_id(const node_id_t *id) {
node_t *n = hash_search(node_id_cache, id);
if(!n) {
node_t tmp = {.id = *id};
n = splay_search(node_id_tree, &tmp);
if(n)
hash_insert(node_id_cache, id, n);
}
return n;
}
node_t *lookup_node_udp(const sockaddr_t *sa) {
return hash_search(node_udp_cache, sa);
}
void update_node_udp(node_t *n, const sockaddr_t *sa) {
if(n == myself) {
logger(DEBUG_ALWAYS, LOG_WARNING, "Trying to update UDP address of myself!");
return;
}
hash_delete(node_udp_cache, &n->address);
if(sa) {
n->address = *sa;
n->sock = 0;
for(int i = 0; i < listen_sockets; i++) {
if(listen_socket[i].sa.sa.sa_family == sa->sa.sa_family) {
n->sock = i;
break;
}
}
hash_insert(node_udp_cache, sa, n);
free(n->hostname);
n->hostname = sockaddr2hostname(&n->address);
logger(DEBUG_PROTOCOL, LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname);
}
/* invalidate UDP information - note that this is a security feature as well to make sure
we can't be tricked into flooding any random address with UDP packets */
n->status.udp_confirmed = false;
n->mtuprobes = 0;
n->minmtu = 0;
n->maxmtu = MTU;
}
bool dump_nodes(connection_t *c) {
for splay_each(node_t, n, node_tree) {
char id[2 * sizeof n->id + 1];
for (size_t c = 0; c < sizeof n->id; ++c)
sprintf(id + 2 * c, "%02hhx", n->id.x[c]);
id[sizeof id - 1] = 0;
send_request(c, "%d %d %s %s %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", CONTROL, REQ_DUMP_NODES,
n->name, id, n->hostname ?: "unknown port unknown",
#ifdef DISABLE_LEGACY
0, 0, 0,
#else
cipher_get_nid(n->outcipher), digest_get_nid(n->outdigest), (int)digest_length(n->outdigest),
#endif
n->outcompression, n->options, bitfield_to_int(&n->status, sizeof n->status),
n->nexthop ? n->nexthop->name : "-", n->via ? n->via->name ?: "-" : "-", n->distance,
n->mtu, n->minmtu, n->maxmtu, (long)n->last_state_change);
}
return send_request(c, "%d %d", CONTROL, REQ_DUMP_NODES);
}
bool dump_traffic(connection_t *c) {
for splay_each(node_t, n, node_tree)
send_request(c, "%d %d %s %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, CONTROL, REQ_DUMP_TRAFFIC,
n->name, n->in_packets, n->in_bytes, n->out_packets, n->out_bytes);
return send_request(c, "%d %d", CONTROL, REQ_DUMP_TRAFFIC);
}