Try harder to connect to unreachable nodes.
[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
31         for splay_each(node_t, n, node_tree) {
32                 if(n == myself || n->connection || !(n->status.has_address || n->status.reachable)) {
33                         continue;
34                 }
35
36                 count++;
37         }
38
39         if(!count) {
40                 return;
41         }
42
43         int r = rand() % count;
44
45         for splay_each(node_t, n, node_tree) {
46                 if(n == myself || n->connection || !(n->status.has_address || n->status.reachable)) {
47                         continue;
48                 }
49
50                 if(r--) {
51                         continue;
52                 }
53
54                 bool found = false;
55
56                 for list_each(outgoing_t, outgoing, outgoing_list) {
57                         if(outgoing->node == n) {
58                                 found = true;
59                                 break;
60                         }
61                 }
62
63                 if(!found) {
64                         logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
65                         outgoing_t *outgoing = xzalloc(sizeof(*outgoing));
66                         outgoing->node = n;
67                         list_insert_tail(outgoing_list, outgoing);
68                         setup_outgoing_connection(outgoing, false);
69                 }
70
71                 break;
72         }
73 }
74
75 static void connect_to_unreachable() {
76         /* Select a random known node. The rationale is that if there are many
77          * reachable nodes, and only a few unreachable nodes, we don't want all
78          * reachable nodes to try to connect to the unreachable ones at the
79          * same time. This way, we back off automatically. Conversely, if there
80          * are only a few reachable nodes, and many unreachable ones, we're
81          * going to try harder to connect to them. */
82
83         int r = rand() % node_tree->count;
84
85         for splay_each(node_t, n, node_tree) {
86                 if(r--) {
87                         continue;
88                 }
89
90                 /* Is it unreachable and do we know an address for it? If not, return. */
91                 if(n == myself || n->connection || n->status.reachable || !n->status.has_address) {
92                         return;
93                 }
94
95                 /* Are we already trying to make an outgoing connection to it? If so, return. */
96                 for list_each(outgoing_t, outgoing, outgoing_list) {
97                         if(outgoing->node == n) {
98                                 return;
99                         }
100                 }
101
102                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
103                 outgoing_t *outgoing = xzalloc(sizeof(*outgoing));
104                 outgoing->node = n;
105                 list_insert_tail(outgoing_list, outgoing);
106                 setup_outgoing_connection(outgoing, false);
107
108                 return;
109         }
110 }
111
112 static void drop_superfluous_outgoing_connection() {
113         /* Choose a random outgoing connection to a node that has at least one other connection. */
114         int count = 0;
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
121                 count++;
122         }
123
124         if(!count) {
125                 return;
126         }
127
128         int r = rand() % count;
129
130         for list_each(connection_t, c, connection_list) {
131                 if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2) {
132                         continue;
133                 }
134
135                 if(r--) {
136                         continue;
137                 }
138
139                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
140                 list_delete(outgoing_list, c->outgoing);
141                 c->outgoing = NULL;
142                 terminate_connection(c, c->edge);
143                 break;
144         }
145 }
146
147 static void drop_superfluous_pending_connections() {
148         for list_each(outgoing_t, o, outgoing_list) {
149                 /* Only look for connections that are waiting to be retried later. */
150                 bool found = false;
151
152                 for list_each(connection_t, c, connection_list) {
153                         if(c->outgoing == o) {
154                                 found = true;
155                                 break;
156                         }
157                 }
158
159                 if(found) {
160                         continue;
161                 }
162
163                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->node->name);
164                 list_delete_node(outgoing_list, node);
165         }
166 }
167
168 void do_autoconnect() {
169         /* Count number of active connections. */
170         int nc = 0;
171
172         for list_each(connection_t, c, connection_list) {
173                 if(c->edge) {
174                         nc++;
175                 }
176         }
177
178         /* Less than 3 connections? Eagerly try to make a new one. */
179         if(nc < 3) {
180                 make_new_connection();
181                 return;
182         }
183
184         /* More than 3 connections? See if we can get rid of a superfluous one. */
185         if(nc > 3) {
186                 drop_superfluous_outgoing_connection();
187         }
188
189         /* Drop pending outgoing connections from the outgoing list. */
190         drop_superfluous_pending_connections();
191
192         /* Check if there are unreachable nodes that we should try to connect to. */
193         connect_to_unreachable();
194 }