2 autoconnect.c -- automatic connection establishment
3 Copyright (C) 2019 Guus Sliepen <guus@meshlink.io>
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.
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.
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.
22 #include "connection.h"
29 /* Make an outgoing connection if possible */
30 static bool make_outgoing(meshlink_handle_t *mesh, node_t *n) {
31 if(!n || n->connection) {
35 n->last_connect_try = mesh->loop.now.tv_sec;
36 logger(mesh, MESHLINK_DEBUG, "Autoconnect trying to connect to %s", n->name);
38 /* check if there is already a connection attempt to this node */
39 for list_each(outgoing_t, outgoing, mesh->outgoings) {
40 if(outgoing->node == n) {
41 logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since it is an outgoing connection already");
46 if(!n->status.reachable && !node_read_public_key(mesh, n)) {
47 logger(mesh, MESHLINK_DEBUG, "* skip autoconnect since we don't know this node's public key");
51 logger(mesh, MESHLINK_DEBUG, "Autoconnecting to %s", n->name);
52 outgoing_t *outgoing = xzalloc(sizeof(outgoing_t));
54 list_insert_tail(mesh->outgoings, outgoing);
55 setup_outgoing_connection(mesh, outgoing);
59 /* Determine if node n is a better candidate for making an early connection to than node m. */
60 static bool compare_candidates(node_t *n, node_t *m) {
61 /* Check if the last connection attempt was successful */
62 bool n_successful = n->last_successful_connection > n->last_connect_try;
63 bool m_successful = n->last_successful_connection > n->last_connect_try;
65 if(n_successful != m_successful) {
69 /* If both were successfully connected to, prefer the most recent one */
70 return n->last_successful_connection > m->last_successful_connection;
72 /* If the last connections were not successful, prefer the one we least recently tried to connect to. */
73 return n->last_connect_try < m->last_connect_try;
78 /* Try to connect to any candidate in the same or better device class. Prefer recently connected-to nodes first. */
79 static bool make_eager_connection(meshlink_handle_t *mesh) {
80 node_t *candidate = NULL;
82 for splay_each(node_t, n, mesh->nodes) {
83 if(n == mesh->self || n->devclass > mesh->devclass || n->connection || n->status.blacklisted) {
92 if(compare_candidates(n, candidate)) {
97 return make_outgoing(mesh, candidate);
100 /* Try to connect to balance connections to different device classes. Prefer recently connected-to nodes first. */
101 static bool make_better_connection(meshlink_handle_t *mesh) {
102 const unsigned int min_connects = mesh->dev_class_traits[mesh->devclass].min_connects;
104 for(dev_class_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
105 unsigned int connects = 0;
107 for list_each(connection_t, c, mesh->connections) {
108 if(c->status.active && c->node && c->node->devclass == devclass) {
111 if(connects >= min_connects) {
117 if(connects >= min_connects) {
121 node_t *candidate = NULL;
123 for splay_each(node_t, n, mesh->nodes) {
124 if(n == mesh->self || n->devclass != devclass || n->connection || n->status.blacklisted) {
133 if(compare_candidates(n, candidate)) {
138 if(make_outgoing(mesh, candidate)) {
146 /* Disconnect from a random node that doesn't weaken the graph, and cancel redundant outgoings */
147 static void disconnect_redundant(meshlink_handle_t *mesh) {
150 for list_each(connection_t, c, mesh->connections) {
151 if(!c->status.active || !c->outgoing || !c->node) {
155 if(c->node->edge_tree->count < 2) {
166 int r = rand() % count;
168 for list_each(connection_t, c, mesh->connections) {
169 if(!c->status.active || !c->outgoing || !c->node) {
173 if(c->node->edge_tree->count < 2) {
181 logger(mesh, MESHLINK_DEBUG, "Autodisconnecting from %s", c->name);
182 list_delete(mesh->outgoings, c->outgoing);
184 terminate_connection(mesh, c, c->status.active);
188 for list_each(outgoing_t, o, mesh->outgoings) {
189 if(!o->node->connection) {
190 list_delete_node(mesh->outgoings, node);
195 static void heal_partitions(meshlink_handle_t *mesh) {
196 /* Select a random known node. The rationale is that if there are many
197 * reachable nodes, and only a few unreachable nodes, we don't want all
198 * reachable nodes to try to connect to the unreachable ones at the
199 * same time. This way, we back off automatically. Conversely, if there
200 * are only a few reachable nodes, and many unreachable ones, we're
201 * going to try harder to connect to them. */
203 int r = rand() % mesh->nodes->count;
205 for splay_each(node_t, n, mesh->nodes) {
210 if(n == mesh->self || n->connection || n->status.reachable || n->status.blacklisted) {
214 /* Are we already trying to make an outgoing connection to it? If so, return. */
215 for list_each(outgoing_t, outgoing, mesh->outgoings) {
216 if(outgoing->node == n) {
221 make_outgoing(mesh, n);
227 unsigned int do_autoconnect(meshlink_handle_t *mesh) {
228 /* Count the number of active connections. */
230 unsigned int cur_connects = 0;
232 for list_each(connection_t, c, mesh->connections) {
233 if(c->status.active) {
238 /* We don't have the minimum number of connections? Eagerly try to make a new one. */
240 const unsigned int max_connects = mesh->dev_class_traits[mesh->devclass].max_connects;
241 const unsigned int min_connects = mesh->dev_class_traits[mesh->devclass].min_connects;
243 logger(mesh, MESHLINK_DEBUG, "do_autoconnect() %d %d %d\n", cur_connects, min_connects, max_connects);
245 if(cur_connects < min_connects) {
246 make_eager_connection(mesh);
247 } else if(cur_connects < max_connects) {
248 /* Otherwise, try to improve. */
249 make_better_connection(mesh);
252 if (cur_connects >= max_connects) {
253 disconnect_redundant(mesh);
256 heal_partitions(mesh);