132467e69eb2b73fe42e81230fa7f527877ebb98
[tinc] / 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(!strcmp(outgoing->name, n->name)) {
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->name = xstrdup(n->name);
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 not, return. */
96                 for list_each(outgoing_t, outgoing, outgoing_list)
97                         if(!strcmp(outgoing->name, n->name)) {
98                                 return;
99                         }
100
101                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
102                 outgoing_t *outgoing = xzalloc(sizeof(*outgoing));
103                 outgoing->name = xstrdup(n->name);
104                 list_insert_tail(outgoing_list, outgoing);
105                 setup_outgoing_connection(outgoing, false);
106
107                 return;
108         }
109 }
110
111 static void drop_superfluous_outgoing_connection() {
112         /* Choose a random outgoing connection to a node that has at least one other connection. */
113         int count = 0;
114
115         for list_each(connection_t, c, connection_list) {
116                 if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2) {
117                         continue;
118                 }
119
120                 count++;
121         }
122
123         if(!count) {
124                 return;
125         }
126
127         int r = rand() % count;
128
129         for list_each(connection_t, c, connection_list) {
130                 if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2) {
131                         continue;
132                 }
133
134                 if(r--) {
135                         continue;
136                 }
137
138                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
139                 list_delete(outgoing_list, c->outgoing);
140                 c->outgoing = NULL;
141                 terminate_connection(c, c->edge);
142                 break;
143         }
144 }
145
146 static void drop_superfluous_pending_connections() {
147         for list_each(outgoing_t, o, outgoing_list) {
148                 /* Only look for connections that are waiting to be retried later. */
149                 bool found = false;
150
151                 for list_each(connection_t, c, connection_list) {
152                         if(c->outgoing == o) {
153                                 found = true;
154                                 break;
155                         }
156                 }
157
158                 if(found) {
159                         continue;
160                 }
161
162                 logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->name);
163                 list_delete_node(outgoing_list, node);
164         }
165 }
166
167 void do_autoconnect() {
168         /* Count number of active connections. */
169         int nc = 0;
170
171         for list_each(connection_t, c, connection_list) {
172                 if(c->edge) {
173                         nc++;
174                 }
175         }
176
177         /* Less than 3 connections? Eagerly try to make a new one. */
178         if(nc < 3) {
179                 make_new_connection();
180                 return;
181         }
182
183         /* More than 3 connections? See if we can get rid of a superfluous one. */
184         if(nc > 3) {
185                 drop_superfluous_outgoing_connection();
186         }
187
188
189         /* Check if there are unreachable nodes that we should try to connect to. */
190         connect_to_unreachable();
191
192         /* Drop pending outgoing connections from the outgoing list. */
193         drop_superfluous_pending_connections();
194 }