Make autoconnect try to heal network splits.
authorGuus Sliepen <guus@tinc-vpn.org>
Tue, 22 Aug 2017 18:51:44 +0000 (20:51 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Tue, 22 Aug 2017 18:51:44 +0000 (20:51 +0200)
When we have less than three connections, we greedily try to connect to any
viable node. However, once we have three connections, try to connect to
nodes that we know of but that aren't reachable.

We also make sure that if there are 100 reachable nodes, and 1 unreachable
one, that not all 100 reachable nodes try to connect to the unreachable
at the same time.

src/Makefile.am
src/autoconnect.c [new file with mode: 0644]
src/autoconnect.h [new file with mode: 0644]
src/net.c

index a98c033..7be46d9 100644 (file)
@@ -42,6 +42,7 @@ chacha_poly1305_SOURCES = \
        chacha-poly1305/poly1305.c chacha-poly1305/poly1305.h
 
 tincd_SOURCES = \
+       autoconnect.c autoconnect.h \
        buffer.c buffer.h \
        cipher.h \
        conf.c conf.h \
diff --git a/src/autoconnect.c b/src/autoconnect.c
new file mode 100644 (file)
index 0000000..1ea51b5
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+    autoconnect.c -- automatic connection establishment
+    Copyright (C) 2017 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 "connection.h"
+#include "logger.h"
+#include "node.h"
+#include "xalloc.h"
+
+static void make_new_connection() {
+       /* Select a random node we haven't connected to yet. */
+       int count = 0;
+       for splay_each(node_t, n, node_tree) {
+               if(n == myself || n->connection || !(n->status.has_address || n->status.reachable))
+                       continue;
+               count++;
+       }
+
+       if(!count)
+               return;
+
+       int r = rand() % count;
+
+       for splay_each(node_t, n, node_tree) {
+               if(n == myself || n->connection || !(n->status.has_address || n->status.reachable))
+                       continue;
+
+               if(r--)
+                       continue;
+
+               bool found = false;
+
+               for list_each(outgoing_t, outgoing, outgoing_list) {
+                       if(!strcmp(outgoing->name, n->name)) {
+                               found = true;
+                               break;
+                       }
+               }
+
+               if(!found) {
+                       logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
+                       outgoing_t *outgoing = xzalloc(sizeof *outgoing);
+                       outgoing->name = xstrdup(n->name);
+                       list_insert_tail(outgoing_list, outgoing);
+                       setup_outgoing_connection(outgoing);
+               }
+
+               break;
+       }
+}
+
+static void connect_to_unreachable() {
+       /* Select a random known node. The rationale is that if there are many
+        * reachable nodes, and only a few unreachable nodes, we don't want all
+        * reachable nodes to try to connect to the unreachable ones at the
+        * same time. This way, we back off automatically. Conversely, if there
+        * are only a few reachable nodes, and many unreachable ones, we're
+        * going to try harder to connect to them. */
+
+       int r = rand() % node_tree->count;
+
+       for splay_each(node_t, n, node_tree) {
+               if(r--)
+                       continue;
+
+               /* Is it unreachable and do we know an address for it? If not, return. */
+               if(n == myself || n->connection || n->status.reachable || !n->status.has_address)
+                       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))
+                               return;
+
+               logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
+               outgoing_t *outgoing = xzalloc(sizeof *outgoing);
+               outgoing->name = xstrdup(n->name);
+               list_insert_tail(outgoing_list, outgoing);
+               setup_outgoing_connection(outgoing);
+
+               return;
+       }
+}
+
+static void drop_superfluous_outgoing_connection() {
+       /* Choose a random outgoing connection to a node that has at least one other connection. */
+       int count = 0;
+       for list_each(connection_t, c, connection_list) {
+               if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2)
+                       continue;
+               count++;
+       }
+
+       if(!count)
+               return;
+
+       int r = rand() % count;
+
+       for list_each(connection_t, c, connection_list) {
+               if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2)
+                       continue;
+               
+               if(r--)
+                       continue;
+
+               logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
+               list_delete(outgoing_list, c->outgoing);
+               c->outgoing = NULL;
+               terminate_connection(c, c->edge);
+               break;
+       }
+}
+
+static void drop_superfluous_pending_connections() {
+       for list_each(outgoing_t, o, outgoing_list) {
+               /* Only look for connections that are waiting to be retried later. */
+               bool found = false;
+               for list_each(connection_t, c, connection_list) {
+                       if(c->outgoing == o) {
+                               found = true;
+                               break;
+                       }
+               }
+
+               if(found)
+                       continue;
+
+               logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->name);
+               list_delete_node(outgoing_list, node);
+       }
+}
+
+void do_autoconnect() {
+       /* Count number of active connections. */
+       int nc = 0;
+       for list_each(connection_t, c, connection_list) {
+               if(c->edge)
+                       nc++;
+       }
+
+       /* Less than 3 connections? Eagerly try to make a new one. */
+       if(nc < 3) {
+               make_new_connection();
+               return;
+       }
+       
+       /* More than 3 connections? See if we can get rid of a superfluous one. */
+       if(nc > 3)
+               drop_superfluous_outgoing_connection();
+
+
+       /* Check if there are unreachable nodes that we should try to connect to. */
+       connect_to_unreachable();
+
+       /* Drop pending outgoing connections from the outgoing list. */
+       drop_superfluous_pending_connections();
+}
diff --git a/src/autoconnect.h b/src/autoconnect.h
new file mode 100644 (file)
index 0000000..dc3ee1c
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+    autoconnect.h -- header for autoconnect.c
+    Copyright (C) 2017 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.
+*/
+
+#ifndef __TINC_AUTOCONNECT_H__
+#define __TINC_AUTOCONNECT_H__
+
+extern void do_autoconnect(void);
+
+#endif
index 8cb7ed7..bda92eb 100644 (file)
--- a/src/net.c
+++ b/src/net.c
@@ -22,7 +22,7 @@
 
 #include "system.h"
 
-#include "utils.h"
+#include "autoconnect.h"
 #include "conf.h"
 #include "connection.h"
 #include "device.h"
@@ -34,6 +34,7 @@
 #include "netutl.h"
 #include "protocol.h"
 #include "subnet.h"
+#include "utils.h"
 #include "xalloc.h"
 
 int contradicting_add_edge = 0;
@@ -245,105 +246,9 @@ static void periodic_handler(void *data) {
 
        /* If AutoConnect is set, check if we need to make or break connections. */
 
-       if(autoconnect && node_tree->count > 1) {
-               /* Count number of active connections */
-               int nc = 0;
-               for list_each(connection_t, c, connection_list) {
-                       if(c->edge)
-                               nc++;
-               }
-
-               if(nc < 3) {
-                       /* Not enough active connections, try to add one.
-                          Choose a random node, if we don't have a connection to it,
-                          and we are not already trying to make one, create an
-                          outgoing connection to this node.
-                       */
-                       int count = 0;
-                       for splay_each(node_t, n, node_tree) {
-                               if(n == myself || n->connection || !(n->status.has_address || n->status.reachable))
-                                       continue;
-                               count++;
-                       }
-
-                       if(!count)
-                               goto end;
-
-                       int r = rand() % count;
-
-                       for splay_each(node_t, n, node_tree) {
-                               if(n == myself || n->connection || !(n->status.has_address || n->status.reachable))
-                                       continue;
-
-                               if(r--)
-                                       continue;
-
-                               bool found = false;
-
-                               for list_each(outgoing_t, outgoing, outgoing_list) {
-                                       if(!strcmp(outgoing->name, n->name)) {
-                                               found = true;
-                                               break;
-                                       }
-                               }
-
-                               if(!found) {
-                                       logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
-                                       outgoing_t *outgoing = xzalloc(sizeof *outgoing);
-                                       outgoing->name = xstrdup(n->name);
-                                       list_insert_tail(outgoing_list, outgoing);
-                                       setup_outgoing_connection(outgoing);
-                               }
-
-                               break;
-                       }
-               } else if(nc > 3) {
-                       /* Too many active connections, try to remove one.
-                          Choose a random outgoing connection to a node
-                          that has at least one other connection.
-                       */
-                       int r = rand() % nc;
-                       int i = 0;
-
-                       for list_each(connection_t, c, connection_list) {
-                               if(!c->edge)
-                                       continue;
-
-                               if(i++ != r)
-                                       continue;
-
-                               if(!c->outgoing || !c->node || c->node->edge_tree->count < 2)
-                                       break;
-
-                               logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
-                               list_delete(outgoing_list, c->outgoing);
-                               c->outgoing = NULL;
-                               terminate_connection(c, c->edge);
-                               break;
-                       }
-               }
-
-               if(nc >= 3) {
-                       /* If we have enough active connections,
-                          remove any pending outgoing connections.
-                       */
-                       for list_each(outgoing_t, o, outgoing_list) {
-                               bool found = false;
-                               for list_each(connection_t, c, connection_list) {
-                                       if(c->outgoing == o) {
-                                               found = true;
-                                               break;
-                                       }
-                               }
-                               if(!found) {
-                                       logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->name);
-                                       list_delete_node(outgoing_list, node);
-                               }
-                       }
-               }
-       }
+       if(autoconnect && node_tree->count > 1)
+               do_autoconnect();
 
-end:
        timeout_set(data, &(struct timeval){5, rand() % 100000});
 }