/*
net.c -- most of the network code
- Copyright (C) 1998-2002 Ivo Timmermans <ivo@o2w.nl>,
- 2000-2002 Guus Sliepen <guus@sliepen.eu.org>
+ Copyright (C) 2014-2017 Guus Sliepen <guus@meshlink.io>
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
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., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- $Id: net.c,v 1.35.4.176 2002/09/04 13:48:51 guus Exp $
+ 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 "config.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#ifdef HAVE_NETINET_IN_SYSTM_H
- #include <netinet/in_systm.h>
-#endif
-#ifdef HAVE_NETINET_IP_H
- #include <netinet/ip.h>
-#endif
-#ifdef HAVE_NETINET_TCP_H
- #include <netinet/tcp.h>
-#endif
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-/* SunOS really wants sys/socket.h BEFORE net/if.h,
- and FreeBSD wants these lines below the rest. */
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <net/if.h>
-
-#include <openssl/rand.h>
-
-#include <utils.h>
-#include <xalloc.h>
-#include <avl_tree.h>
-#include <list.h>
+#include "system.h"
+#include "utils.h"
#include "conf.h"
#include "connection.h"
+#include "devtools.h"
+#include "graph.h"
+#include "logger.h"
+#include "meshlink_internal.h"
#include "meta.h"
#include "net.h"
#include "netutl.h"
-#include "process.h"
#include "protocol.h"
-#include "subnet.h"
-#include "graph.h"
-#include "process.h"
-#include "route.h"
-#include "device.h"
-#include "event.h"
+#include "sptps.h"
+#include "xalloc.h"
-#include "system.h"
+#include <assert.h>
-#ifndef HAVE_RAND_PSEUDO_BYTES
-#define RAND_pseudo_bytes RAND_bytes
+#if !defined(min)
+static inline int min(int a, int b) {
+ return a < b ? a : b;
+}
#endif
-int do_purge = 0;
-int sighup = 0;
-int sigalrm = 0;
-
-time_t now = 0;
-
-/* Purge edges and subnets of unreachable nodes. Use carefully. */
-
-void purge(void)
-{
- avl_node_t *nnode, *nnext, *enode, *enext, *snode, *snext, *cnode;
- node_t *n;
- edge_t *e;
- subnet_t *s;
- connection_t *c;
-cp
- if(debug_lvl >= DEBUG_PROTOCOL)
- syslog(LOG_DEBUG, _("Purging unreachable nodes"));
-
- for(nnode = node_tree->head; nnode; nnode = nnext)
- {
- nnext = nnode->next;
- n = (node_t *)nnode->data;
-
- if(!n->status.reachable)
- {
- if(debug_lvl >= DEBUG_SCARY_THINGS)
- syslog(LOG_DEBUG, _("Purging node %s (%s)"), n->name, n->hostname);
-
- for(snode = n->subnet_tree->head; snode; snode = snext)
- {
- snext = snode->next;
- s = (subnet_t *)snode->data;
-
- for(cnode = connection_tree->head; cnode; cnode = cnode->next)
- {
- c = (connection_t *)cnode->data;
- if(c->status.active)
- send_del_subnet(c, s);
- }
-
- subnet_del(n, s);
- }
-
- for(enode = n->edge_tree->head; enode; enode = enext)
- {
- enext = enode->next;
- e = (edge_t *)enode->data;
-
- for(cnode = connection_tree->head; cnode; cnode = cnode->next)
- {
- c = (connection_t *)cnode->data;
- if(c->status.active)
- send_del_edge(c, e);
- }
-
- edge_del(e);
- }
-
- node_del(n);
- }
- }
-cp
-}
-
-/*
- put all file descriptors in an fd_set array
- While we're at it, purge stuff that needs to be removed.
-*/
-void build_fdset(fd_set *fs)
-{
- avl_node_t *node, *next;
- connection_t *c;
- int i;
-cp
- FD_ZERO(fs);
-
- for(node = connection_tree->head; node; node = next)
- {
- next = node->next;
- c = (connection_t *)node->data;
-
- if(c->status.remove)
- {
- connection_del(c);
- if(!connection_tree->head)
- purge();
- }
- else
- FD_SET(c->socket, fs);
- }
-
- for(i = 0; i < listen_sockets; i++)
- {
- FD_SET(listen_socket[i].tcp, fs);
- FD_SET(listen_socket[i].udp, fs);
- }
-
- FD_SET(device_fd, fs);
-cp
-}
+static const int default_timeout = 5;
+static const int default_interval = 60;
/*
Terminate a connection:
- - Close the socket
- - Remove associated edge and tell other connections about it if report = 1
+ - Mark it as inactive
+ - Remove the edge representing this connection
+ - Kill it with fire
- Check if we need to retry making an outgoing connection
- - Deactivate the host
*/
-void terminate_connection(connection_t *c, int report)
-{
- avl_node_t *node;
- connection_t *other;
-cp
- if(c->status.remove)
- return;
-
- if(debug_lvl >= DEBUG_CONNECTIONS)
- syslog(LOG_NOTICE, _("Closing connection with %s (%s)"),
- c->name, c->hostname);
-
- c->status.remove = 1;
- c->status.active = 0;
-
- if(c->node)
- c->node->connection = NULL;
-
- if(c->socket)
- close(c->socket);
-
- if(c->edge)
- {
- if(report)
- {
- for(node = connection_tree->head; node; node = node->next)
- {
- other = (connection_t *)node->data;
- if(other->status.active && other != c)
- send_del_edge(other, c->edge);
- }
- }
-
- edge_del(c->edge);
-
- /* Run MST and SSSP algorithms */
-
- graph();
- }
-
- /* Check if this was our outgoing connection */
-
- if(c->outgoing)
- {
- retry_outgoing(c->outgoing);
- c->outgoing = NULL;
- }
-cp
+void terminate_connection(meshlink_handle_t *mesh, connection_t *c, bool report) {
+ logger(mesh, MESHLINK_INFO, "Closing connection with %s", c->name);
+
+ c->status.active = false;
+
+ if(c->node && c->node->connection == c) {
+ c->node->connection = NULL;
+ }
+
+ if(c->edge) {
+ if(report) {
+ send_del_edge(mesh, mesh->everyone, c->edge, 0);
+ }
+
+ edge_del(mesh, c->edge);
+ c->edge = NULL;
+
+ /* Run MST and SSSP algorithms */
+
+ graph(mesh);
+
+ /* If the node is not reachable anymore but we remember it had an edge to us, clean it up */
+
+ if(report && c->node && !c->node->status.reachable) {
+ edge_t *e;
+ e = lookup_edge(c->node, mesh->self);
+
+ if(e) {
+ send_del_edge(mesh, mesh->everyone, e, 0);
+ edge_del(mesh, e);
+ }
+ }
+ }
+
+ outgoing_t *outgoing = c->outgoing;
+ connection_del(mesh, c);
+
+ /* Check if this was our outgoing connection */
+
+ if(outgoing) {
+ do_outgoing_connection(mesh, outgoing);
+ }
}
/*
end does not reply in time, we consider them dead
and close the connection.
*/
-void check_dead_connections(void)
-{
- avl_node_t *node, *next;
- connection_t *c;
-cp
- for(node = connection_tree->head; node; node = next)
- {
- next = node->next;
- c = (connection_t *)node->data;
- if(c->last_ping_time + pingtimeout < now)
- {
- if(c->status.active)
- {
- if(c->status.pinged)
- {
- if(debug_lvl >= DEBUG_PROTOCOL)
- syslog(LOG_INFO, _("%s (%s) didn't respond to PING"),
- c->name, c->hostname);
- c->status.timeout = 1;
- terminate_connection(c, 1);
- }
- else
- {
- send_ping(c);
- }
- }
- else
- {
- if(debug_lvl >= DEBUG_CONNECTIONS)
- syslog(LOG_WARNING, _("Timeout from %s (%s) during authentication"),
- c->name, c->hostname);
- terminate_connection(c, 0);
- }
- }
- }
-cp
+static void timeout_handler(event_loop_t *loop, void *data) {
+ assert(data);
+
+ meshlink_handle_t *mesh = loop->data;
+ logger(mesh, MESHLINK_DEBUG, "timeout_handler()");
+
+ for list_each(connection_t, c, mesh->connections) {
+ int pingtimeout = c->node ? mesh->dev_class_traits[c->node->devclass].pingtimeout : default_timeout;
+ int pinginterval = c->node ? mesh->dev_class_traits[c->node->devclass].pinginterval : default_interval;
+
+ if(c->outgoing && c->outgoing->timeout < 5) {
+ pingtimeout = 1;
+ }
+
+ // Also make sure that if outstanding key requests for the UDP counterpart of a connection has timed out, we restart it.
+ if(c->node) {
+ if(c->node->status.waitingforkey && c->node->last_req_key + pingtimeout <= mesh->loop.now.tv_sec) {
+ send_req_key(mesh, c->node);
+ }
+ }
+
+ if(c->status.active && c->last_key_renewal + 3600 < mesh->loop.now.tv_sec) {
+ devtool_sptps_renewal_probe((meshlink_node_t *)c->node);
+
+ if(!sptps_force_kex(&c->sptps)) {
+ logger(mesh, MESHLINK_ERROR, "SPTPS key renewal for connection with %s failed", c->name);
+ terminate_connection(mesh, c, true);
+ continue;
+ } else {
+ c->last_key_renewal = mesh->loop.now.tv_sec;
+ }
+ }
+
+ if(c->last_ping_time + pingtimeout <= mesh->loop.now.tv_sec) {
+ if(c->status.active) {
+ if(c->status.pinged) {
+ logger(mesh, MESHLINK_INFO, "%s didn't respond to PING in %ld seconds", c->name, (long)mesh->loop.now.tv_sec - c->last_ping_time);
+ } else if(c->last_ping_time + pinginterval <= mesh->loop.now.tv_sec) {
+ send_ping(mesh, c);
+ continue;
+ } else {
+ continue;
+ }
+ } else {
+ if(c->status.connecting) {
+ logger(mesh, MESHLINK_WARNING, "Timeout while connecting to %s", c->name);
+ } else {
+ logger(mesh, MESHLINK_WARNING, "Timeout from %s during authentication", c->name);
+ }
+ }
+
+ terminate_connection(mesh, c, c->status.active);
+ }
+ }
+
+ timeout_set(&mesh->loop, data, &(struct timespec) {
+ 1, prng(mesh, TIMER_FUDGE)
+ });
+}
+
+// devclass asc, last_successfull_connection desc
+static int node_compare_devclass_asc_lsc_desc(const void *a, const void *b) {
+ const node_t *na = a, *nb = b;
+
+ if(na->devclass < nb->devclass) {
+ return -1;
+ }
+
+ if(na->devclass > nb->devclass) {
+ return 1;
+ }
+
+ if(na->last_successfull_connection == nb->last_successfull_connection) {
+ return 0;
+ }
+
+ if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) {
+ return -1;
+ }
+
+ if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) {
+ return 1;
+ }
+
+ if(na < nb) {
+ return -1;
+ }
+
+ if(na > nb) {
+ return 1;
+ }
+
+ return 0;
}
+// last_successfull_connection desc
+static int node_compare_lsc_desc(const void *a, const void *b) {
+ const node_t *na = a, *nb = b;
+
+ if(na->last_successfull_connection == nb->last_successfull_connection) {
+ return 0;
+ }
+
+ if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) {
+ return -1;
+ }
+
+ if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) {
+ return 1;
+ }
+
+ if(na < nb) {
+ return -1;
+ }
+
+ if(na > nb) {
+ return 1;
+ }
+
+ return 0;
+}
+
+// devclass desc
+static int node_compare_devclass_desc(const void *a, const void *b) {
+ const node_t *na = a, *nb = b;
+
+ if(na->devclass < nb->devclass) {
+ return -1;
+ }
+
+ if(na->devclass > nb->devclass) {
+ return 1;
+ }
+
+ if(na < nb) {
+ return -1;
+ }
+
+ if(na > nb) {
+ return 1;
+ }
+
+ return 0;
+}
+
+
/*
- check all connections to see if anything
- happened on their sockets
-*/
-void check_network_activity(fd_set *f)
+
+autoconnect()
{
- connection_t *c;
- avl_node_t *node;
- int result, i;
- int len = sizeof(result);
- vpn_packet_t packet;
-cp
- if(FD_ISSET(device_fd, f))
- {
- if(!read_packet(&packet))
- route_outgoing(&packet);
- }
-
- for(node = connection_tree->head; node; node = node->next)
- {
- c = (connection_t *)node->data;
-
- if(c->status.remove)
- continue;
-
- if(FD_ISSET(c->socket, f))
- {
- if(c->status.connecting)
- {
- c->status.connecting = 0;
- getsockopt(c->socket, SOL_SOCKET, SO_ERROR, &result, &len);
- if(!result)
- finish_connecting(c);
- else
- {
- if(debug_lvl >= DEBUG_CONNECTIONS)
- syslog(LOG_DEBUG, _("Error while connecting to %s (%s): %s"), c->name, c->hostname, strerror(result));
- close(c->socket);
- do_outgoing_connection(c);
- continue;
- }
- }
- if(receive_meta(c) < 0)
- {
- terminate_connection(c, c->status.active);
- continue;
- }
- }
- }
-
- for(i = 0; i < listen_sockets; i++)
- {
- if(FD_ISSET(listen_socket[i].udp, f))
- handle_incoming_vpn_data(listen_socket[i].udp);
- if(FD_ISSET(listen_socket[i].tcp, f))
- handle_new_meta_connection(listen_socket[i].tcp);
- }
-cp
+ timeout = 5
+
+ // find the best one for initial connect
+
+ if cur < min
+ newcon =
+ first from nodes
+ where dclass <= my.dclass and !connection and (timestamp - last_retry) > retry_timeout
+ order by dclass asc, last_connection desc
+ if newcon
+ timeout = 0
+ goto connect
+
+
+ // find better nodes to connect to: in case we have less than min connections within [BACKBONE, i] and there are nodes which we are not connected to within the range
+
+ if min <= cur < max
+ j = 0
+ for i = BACKBONE to my.dclass
+ j += count(from connections where node.dclass = i)
+ if j < min
+ newcon =
+ first from nodes
+ where dclass = i and !connection and (timestamp - last_retry) > retry_timeout
+ order by last_connection desc
+ if newcon
+ goto connect
+ else
+ break
+
+
+ // heal partitions
+
+ if min <= cur < max
+ newcon =
+ first from nodes
+ where dclass <= my.dclass and !reachable and (timestamp - last_retry) > retry_timeout
+ order by dclass asc, last_connection desc
+ if newcon
+ goto connect
+
+
+ // connect
+
+connect:
+ if newcon
+ connect newcon
+
+
+ // disconnect outgoing connections in case we have more than min connections within [BACKBONE, i] and there are nodes which we are connected to within the range [i, PORTABLE]
+
+ if min < cur <= max
+ j = 0
+ for i = BACKBONE to my.dclass
+ j += count(from connections where node.dclass = i)
+ if min < j
+ delcon =
+ first from nodes
+ where dclass >= i and outgoing_connection
+ order by dclass desc
+ if disconnect
+ goto disconnect
+ else
+ break
+
+
+ // disconnect connections in case we have more than enough connections
+
+ if max < cur
+ delcon =
+ first from nodes
+ where outgoing_connection
+ order by dclass desc
+ goto disconnect
+
+ // disconnect
+
+disconnect
+ if delcon
+ disconnect delcon
+
+
+ // next iteration
+ next (timeout, autoconnect)
+
+}
+
+*/
+
+
+static void periodic_handler(event_loop_t *loop, void *data) {
+ meshlink_handle_t *mesh = loop->data;
+
+ /* Check if there are too many contradicting ADD_EDGE and DEL_EDGE messages.
+ This usually only happens when another node has the same Name as this node.
+ If so, sleep for a short while to prevent a storm of contradicting messages.
+ */
+
+ if(mesh->contradicting_del_edge > 100 && mesh->contradicting_add_edge > 100) {
+ logger(mesh, MESHLINK_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", mesh->sleeptime);
+ struct timespec ts = {mesh->sleeptime, 0};
+ nanosleep(&ts, NULL);
+ mesh->sleeptime *= 2;
+
+ if(mesh->sleeptime < 0) {
+ mesh->sleeptime = 3600;
+ }
+ } else {
+ mesh->sleeptime /= 2;
+
+ if(mesh->sleeptime < 10) {
+ mesh->sleeptime = 10;
+ }
+ }
+
+ mesh->contradicting_add_edge = 0;
+ mesh->contradicting_del_edge = 0;
+
+ int timeout = default_timeout;
+
+ /* Check if we need to make or break connections. */
+
+ if(mesh->nodes->count > 1) {
+
+ logger(mesh, MESHLINK_DEBUG, "--- autoconnect begin ---");
+
+ int retry_timeout = min(mesh->nodes->count * default_timeout, 60);
+
+ logger(mesh, MESHLINK_DEBUG, "* devclass = %d", mesh->devclass);
+ logger(mesh, MESHLINK_DEBUG, "* nodes = %d", mesh->nodes->count);
+ logger(mesh, MESHLINK_DEBUG, "* retry_timeout = %d", retry_timeout);
+
+
+ // connect disconnect nodes
+
+ node_t *connect_to = NULL;
+ node_t *disconnect_from = NULL;
+
+
+ // get cur_connects
+
+ unsigned int cur_connects = 0;
+
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->status.active) {
+ cur_connects += 1;
+ }
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "* cur_connects = %d", cur_connects);
+ logger(mesh, MESHLINK_DEBUG, "* outgoings = %d", mesh->outgoings->count);
+
+ // get min_connects and max_connects
+
+ unsigned int min_connects = mesh->dev_class_traits[mesh->devclass].min_connects;
+ unsigned int max_connects = mesh->dev_class_traits[mesh->devclass].max_connects;
+
+ logger(mesh, MESHLINK_DEBUG, "* min_connects = %d", min_connects);
+ logger(mesh, MESHLINK_DEBUG, "* max_connects = %d", max_connects);
+
+ // find the best one for initial connect
+
+ if(cur_connects < min_connects) {
+ splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL);
+
+ for splay_each(node_t, n, mesh->nodes) {
+ logger(mesh, MESHLINK_DEBUG, "* %s->devclass = %d", n->name, n->devclass);
+
+ if(n != mesh->self && n->devclass <= mesh->devclass && !n->connection && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) {
+ splay_insert(nodes, n);
+ }
+ }
+
+ if(nodes->head) {
+ //timeout = 0;
+ connect_to = (node_t *)nodes->head->data;
+
+ logger(mesh, MESHLINK_DEBUG, "* found best one for initial connect: %s", connect_to->name);
+ } else {
+ logger(mesh, MESHLINK_DEBUG, "* could not find node for initial connect");
+ }
+
+ splay_delete_tree(nodes);
+ }
+
+
+ // find better nodes to connect to
+
+ if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) {
+ unsigned int connects = 0;
+
+ for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->status.active && c->node && c->node->devclass == devclass) {
+ connects += 1;
+ }
+ }
+
+ if(connects < min_connects) {
+ splay_tree_t *nodes = splay_alloc_tree(node_compare_lsc_desc, NULL);
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(n != mesh->self && n->devclass == devclass && !n->connection && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) {
+ splay_insert(nodes, n);
+ }
+ }
+
+ if(nodes->head) {
+ logger(mesh, MESHLINK_DEBUG, "* found better node");
+ connect_to = (node_t *)nodes->head->data;
+
+ splay_delete_tree(nodes);
+ break;
+ }
+
+ splay_delete_tree(nodes);
+ } else {
+ break;
+ }
+ }
+
+ if(!connect_to) {
+ logger(mesh, MESHLINK_DEBUG, "* could not find better nodes");
+ }
+ }
+
+
+ // heal partitions
+
+ if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) {
+ splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL);
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(n != mesh->self && n->devclass <= mesh->devclass && !n->status.reachable && !n->status.blacklisted && (n->last_connect_try == 0 || (mesh->loop.now.tv_sec - n->last_connect_try) > retry_timeout)) {
+ splay_insert(nodes, n);
+ }
+ }
+
+ if(nodes->head) {
+ logger(mesh, MESHLINK_DEBUG, "* try to heal partition");
+ connect_to = (node_t *)nodes->head->data;
+ } else {
+ logger(mesh, MESHLINK_DEBUG, "* could not find nodes for partition healing");
+ }
+
+ splay_delete_tree(nodes);
+ }
+
+
+ // perform connect
+
+ if(connect_to && !connect_to->connection) {
+ connect_to->last_connect_try = mesh->loop.now.tv_sec;
+ logger(mesh, MESHLINK_DEBUG, "Autoconnect trying to connect to %s", connect_to->name);
+
+ /* check if there is already a connection attempt to this node */
+ bool skip = false;
+
+ for list_each(outgoing_t, outgoing, mesh->outgoings) {
+ if(outgoing->node == connect_to) {
+ logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since it is an outgoing connection already");
+ skip = true;
+ break;
+ }
+ }
+
+ if(!connect_to->status.reachable && !node_read_public_key(mesh, connect_to)) {
+ logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since we don't know this node's public key");
+ skip = true;
+ }
+
+ if(!skip) {
+ logger(mesh, MESHLINK_DEBUG, "Autoconnecting to %s", connect_to->name);
+ outgoing_t *outgoing = xzalloc(sizeof(outgoing_t));
+ outgoing->node = connect_to;
+ list_insert_tail(mesh->outgoings, outgoing);
+ setup_outgoing_connection(mesh, outgoing);
+ }
+ }
+
+
+ // disconnect suboptimal outgoing connections
+
+ if(min_connects < cur_connects /*&& cur_connects <= max_connects*/) {
+ unsigned int connects = 0;
+
+ for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->status.active && c->node && c->node->devclass == devclass) {
+ connects += 1;
+ }
+ }
+
+ if(min_connects < connects) {
+ splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL);
+
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->outgoing && c->node && c->node->devclass >= devclass) {
+ splay_insert(nodes, c->node);
+ }
+ }
+
+ if(nodes->head) {
+ logger(mesh, MESHLINK_DEBUG, "* disconnect suboptimal outgoing connection");
+ disconnect_from = (node_t *)nodes->head->data;
+ }
+
+ splay_delete_tree(nodes);
+ break;
+ }
+ }
+
+ if(!disconnect_from) {
+ logger(mesh, MESHLINK_DEBUG, "* no suboptimal outgoing connections");
+ }
+ }
+
+
+ // disconnect connections (too many connections)
+
+ if(!disconnect_from && max_connects < cur_connects) {
+ splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL);
+
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->status.active && c->node) {
+ splay_insert(nodes, c->node);
+ }
+ }
+
+ if(nodes->head) {
+ logger(mesh, MESHLINK_DEBUG, "* disconnect connection (too many connections)");
+
+ //timeout = 0;
+ disconnect_from = (node_t *)nodes->head->data;
+ } else {
+ logger(mesh, MESHLINK_DEBUG, "* no node we want to disconnect, even though we have too many connections");
+ }
+
+ splay_delete_tree(nodes);
+ }
+
+
+ // perform disconnect
+
+ if(disconnect_from && disconnect_from->connection) {
+ logger(mesh, MESHLINK_DEBUG, "Autodisconnecting from %s", disconnect_from->connection->name);
+ list_delete(mesh->outgoings, disconnect_from->connection->outgoing);
+ disconnect_from->connection->outgoing = NULL;
+ terminate_connection(mesh, disconnect_from->connection, disconnect_from->connection->status.active);
+ }
+
+ // reduce timeout if we don't have enough connections + outgoings
+ if(cur_connects + mesh->outgoings->count < 3) {
+ timeout = 1;
+ }
+
+ // done!
+
+ logger(mesh, MESHLINK_DEBUG, "--- autoconnect end ---");
+ }
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(n->status.dirty) {
+ if(!node_write_config(mesh, n)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not update %s", n->name);
+ }
+
+ n->status.dirty = false;
+ }
+
+ if(n->status.validkey && n->last_req_key + 3600 < mesh->loop.now.tv_sec) {
+ logger(mesh, MESHLINK_DEBUG, "SPTPS key renewal for node %s", n->name);
+ devtool_sptps_renewal_probe((meshlink_node_t *)n);
+
+ if(!sptps_force_kex(&n->sptps)) {
+ logger(mesh, MESHLINK_ERROR, "SPTPS key renewal for node %s failed", n->name);
+ n->status.validkey = false;
+ sptps_stop(&n->sptps);
+ n->status.waitingforkey = false;
+ n->last_req_key = -3600;
+ } else {
+ n->last_req_key = mesh->loop.now.tv_sec;
+ }
+ }
+ }
+
+ timeout_set(&mesh->loop, data, &(struct timespec) {
+ timeout, prng(mesh, TIMER_FUDGE)
+ });
+}
+
+void handle_meta_connection_data(meshlink_handle_t *mesh, connection_t *c) {
+ if(!receive_meta(mesh, c)) {
+ terminate_connection(mesh, c, c->status.active);
+ return;
+ }
+}
+
+void retry(meshlink_handle_t *mesh) {
+ /* Reset the reconnection timers for all outgoing connections */
+ for list_each(outgoing_t, outgoing, mesh->outgoings) {
+ outgoing->timeout = 0;
+
+ if(outgoing->ev.cb) {
+ timeout_set(&mesh->loop, &outgoing->ev, &(struct timespec) {
+ 0, 0
+ });
+ }
+ }
+
+ /* For active connections, check if their addresses are still valid.
+ * If yes, reset their ping timers, otherwise terminate them. */
+ for list_each(connection_t, c, mesh->connections) {
+ if(!c->status.active) {
+ continue;
+ }
+
+ if(!c->status.pinged) {
+ c->last_ping_time = -3600;
+ }
+
+ sockaddr_t sa;
+ socklen_t salen = sizeof(sa);
+
+ if(getsockname(c->socket, &sa.sa, &salen)) {
+ continue;
+ }
+
+ switch(sa.sa.sa_family) {
+ case AF_INET:
+ sa.in.sin_port = 0;
+ break;
+
+ case AF_INET6:
+ sa.in6.sin6_port = 0;
+ break;
+
+ default:
+ continue;
+ }
+
+ int sock = socket(sa.sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
+
+ if(sock != -1) {
+ continue;
+ }
+
+ if(bind(sock, &sa.sa, salen) && errno == EADDRNOTAVAIL) {
+ logger(mesh, MESHLINK_DEBUG, "Local address for connection to %s no longer valid, terminating", c->name);
+ terminate_connection(mesh, c, c->status.active);
+ }
+
+ closesocket(sock);
+ }
+
+ /* Kick the ping timeout handler */
+ timeout_set(&mesh->loop, &mesh->pingtimer, &(struct timespec) {
+ 0, 0
+ });
}
/*
this is where it all happens...
*/
-void main_loop(void)
-{
- fd_set fset;
- struct timeval tv;
- int r;
- time_t last_ping_check;
- event_t *event;
-cp
- last_ping_check = now;
-
- srand(now);
-
- for(;;)
- {
- now = time(NULL);
-
- tv.tv_sec = 1 + (rand() & 7); /* Approx. 5 seconds, randomized to prevent global synchronisation effects */
- tv.tv_usec = 0;
-
- build_fdset(&fset);
-
- if((r = select(FD_SETSIZE, &fset, NULL, NULL, &tv)) < 0)
- {
- if(errno != EINTR && errno != EAGAIN)
- {
- syslog(LOG_ERR, _("Error while waiting for input: %s"), strerror(errno));
- cp_trace();
- dump_connections();
- return;
- }
-
- continue;
- }
-
- check_network_activity(&fset);
-
- if(do_purge)
- {
- purge();
- do_purge = 0;
- }
-
- /* Let's check if everybody is still alive */
-
- if(last_ping_check + pingtimeout < now)
- {
- check_dead_connections();
- last_ping_check = now;
-
- if(routing_mode== RMODE_SWITCH)
- age_mac();
-
- age_past_requests();
-
- /* Should we regenerate our key? */
-
- if(keyexpires < now)
- {
- if(debug_lvl >= DEBUG_STATUS)
- syslog(LOG_INFO, _("Regenerating symmetric key"));
-
- RAND_pseudo_bytes(myself->key, myself->keylength);
- send_key_changed(myself->connection, myself);
- keyexpires = now + keylifetime;
- }
- }
-
-
- while((event = get_expired_event()))
- {
- event->handler(event->data);
- free(event);
- }
-
- if(sigalrm)
- {
- syslog(LOG_INFO, _("Flushing event queue"));
-
- while(event_tree->head)
- {
- event = (event_t *)event_tree->head->data;
- event->handler(event->data);
- event_del(event);
- }
- sigalrm = 0;
- }
-
- if(sighup)
- {
- sighup = 0;
- close_network_connections();
- exit_configuration(&config_tree);
-
- syslog(LOG_INFO, _("Rereading configuration file and restarting in 5 seconds..."));
- sleep(5);
-
- init_configuration(&config_tree);
-
- if(read_server_config())
- {
- syslog(LOG_ERR, _("Unable to reread configuration file, exitting."));
- exit(1);
- }
-
- if(setup_network_connections())
- return;
-
- continue;
- }
- }
-cp
+void main_loop(meshlink_handle_t *mesh) {
+ timeout_add(&mesh->loop, &mesh->pingtimer, timeout_handler, &mesh->pingtimer, &(struct timespec) {
+ 1, prng(mesh, TIMER_FUDGE)
+ });
+ timeout_add(&mesh->loop, &mesh->periodictimer, periodic_handler, &mesh->periodictimer, &(struct timespec) {
+ 0, 0
+ });
+
+ //Add signal handler
+ mesh->datafromapp.signum = 0;
+ signal_add(&mesh->loop, &mesh->datafromapp, meshlink_send_from_queue, mesh, mesh->datafromapp.signum);
+
+ if(!event_loop_run(&mesh->loop, &mesh->mutex)) {
+ logger(mesh, MESHLINK_ERROR, "Error while waiting for input: %s", strerror(errno));
+ call_error_cb(mesh, MESHLINK_ENETWORK);
+ }
+
+ signal_del(&mesh->loop, &mesh->datafromapp);
+ timeout_del(&mesh->loop, &mesh->periodictimer);
+ timeout_del(&mesh->loop, &mesh->pingtimer);
}