Make autoconnect try to heal network splits.
[oweals/tinc.git] / src / autoconnect.c
1 /*
2     autoconnect.c -- automatic connection establishment
3     Copyright (C) 2017 Guus Sliepen <guus@tinc-vpn.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21
22 #include "connection.h"
23 #include "logger.h"
24 #include "node.h"
25 #include "xalloc.h"
26
27 static void make_new_connection() {
28         /* Select a random node we haven't connected to yet. */
29         int count = 0;
30         for splay_each(node_t, n, node_tree) {
31                 if(n == myself || n->connection || !(n->status.has_address || n->status.reachable))
32                         continue;
33                 count++;
34         }
35
36         if(!count)
37                 return;
38
39         int r = rand() % count;
40
41         for splay_each(node_t, n, node_tree) {
42                 if(n == myself || n->connection || !(n->status.has_address || n->status.reachable))
43                         continue;
44
45                 if(r--)
46                         continue;
47
48                 bool found = false;
49
50                 for list_each(outgoing_t, outgoing, outgoing_list) {
51                         if(!strcmp(outgoing->name, n->name)) {
52                                 found = true;
53                                 break;
54                         }
55                 }
56
57                 if(!found) {
58                         logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
59                         outgoing_t *outgoing = xzalloc(sizeof *outgoing);
60                         outgoing->name = xstrdup(n->name);
61                         list_insert_tail(outgoing_list, outgoing);
62                         setup_outgoing_connection(outgoing);
63                 }
64
65                 break;
66         }
67 }
68
69 static void connect_to_unreachable() {
70         /* Select a random known node. The rationale is that if there are many
71          * reachable nodes, and only a few unreachable nodes, we don't want all
72          * reachable nodes to try to connect to the unreachable ones at the
73          * same time. This way, we back off automatically. Conversely, if there
74          * are only a few reachable nodes, and many unreachable ones, we're
75          * going to try harder to connect to them. */
76
77         int r = rand() % node_tree->count;
78
79         for splay_each(node_t, n, node_tree) {
80                 if(r--)
81                         continue;
82
83                 /* Is it unreachable and do we know an address for it? If not, return. */
84                 if(n == myself || n->connection || n->status.reachable || !n->status.has_address)
85                         return;
86
87                 /* Are we already trying to make an outgoing connection to it? If not, return. */
88                 for list_each(outgoing_t, outgoing, outgoing_list)
89                         if(!strcmp(outgoing->name, n->name))
90                                 return;
91
92                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
93                 outgoing_t *outgoing = xzalloc(sizeof *outgoing);
94                 outgoing->name = xstrdup(n->name);
95                 list_insert_tail(outgoing_list, outgoing);
96                 setup_outgoing_connection(outgoing);
97
98                 return;
99         }
100 }
101
102 static void drop_superfluous_outgoing_connection() {
103         /* Choose a random outgoing connection to a node that has at least one other connection. */
104         int count = 0;
105         for list_each(connection_t, c, connection_list) {
106                 if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2)
107                         continue;
108                 count++;
109         }
110
111         if(!count)
112                 return;
113
114         int r = rand() % count;
115
116         for list_each(connection_t, c, connection_list) {
117                 if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2)
118                         continue;
119                 
120                 if(r--)
121                         continue;
122
123                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
124                 list_delete(outgoing_list, c->outgoing);
125                 c->outgoing = NULL;
126                 terminate_connection(c, c->edge);
127                 break;
128         }
129 }
130
131 static void drop_superfluous_pending_connections() {
132         for list_each(outgoing_t, o, outgoing_list) {
133                 /* Only look for connections that are waiting to be retried later. */
134                 bool found = false;
135                 for list_each(connection_t, c, connection_list) {
136                         if(c->outgoing == o) {
137                                 found = true;
138                                 break;
139                         }
140                 }
141
142                 if(found)
143                         continue;
144
145                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->name);
146                 list_delete_node(outgoing_list, node);
147         }
148 }
149
150 void do_autoconnect() {
151         /* Count number of active connections. */
152         int nc = 0;
153         for list_each(connection_t, c, connection_list) {
154                 if(c->edge)
155                         nc++;
156         }
157
158         /* Less than 3 connections? Eagerly try to make a new one. */
159         if(nc < 3) {
160                 make_new_connection();
161                 return;
162         }
163         
164         /* More than 3 connections? See if we can get rid of a superfluous one. */
165         if(nc > 3)
166                 drop_superfluous_outgoing_connection();
167
168
169         /* Check if there are unreachable nodes that we should try to connect to. */
170         connect_to_unreachable();
171
172         /* Drop pending outgoing connections from the outgoing list. */
173         drop_superfluous_pending_connections();
174 }