]> git.meshlink.io Git - meshlink/blobdiff - src/net.c
Merge branch 'mesh_topology_output' into roles
[meshlink] / src / net.c
index c63fbacd756cf2f73720e9bc44742ebf53be87ee..80e866a0ad461fc49bce1794379044ca97e2e33c 100644 (file)
--- a/src/net.c
+++ b/src/net.c
 #include "protocol.h"
 #include "xalloc.h"
 
+#include <assert.h>
+
+static const int min(int a, int b) {
+       return a < b ? a : b;
+}
+
 /*
   Terminate a connection:
   - Mark it as inactive
@@ -39,7 +45,7 @@
   - Check if we need to retry making an outgoing connection
 */
 void terminate_connection(meshlink_handle_t *mesh, connection_t *c, bool report) {
-       logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Closing connection with %s (%s)", c->name, c->hostname);
+       logger(mesh, MESHLINK_INFO, "Closing connection with %s (%s)", c->name, c->hostname);
 
        c->status.active = false;
 
@@ -94,12 +100,18 @@ void terminate_connection(meshlink_handle_t *mesh, connection_t *c, bool report)
 */
 static void timeout_handler(event_loop_t *loop, void *data) {
        meshlink_handle_t *mesh = loop->data;
+       logger(mesh, MESHLINK_DEBUG, "timeout_handler()");
 
        for list_each(connection_t, c, mesh->connections) {
+               // Also make sure that if outstanding key requests for the UDP counterpart of a connection has timed out, we restart it.
+               if(c->node) {
+                       if(c->node->status.waitingforkey && c->node->last_req_key + mesh->pingtimeout <= mesh->loop.now.tv_sec)
+                               send_req_key(mesh, c->node);
+               }
                if(c->last_ping_time + mesh->pingtimeout <= mesh->loop.now.tv_sec) {
                        if(c->status.active) {
                                if(c->status.pinged) {
-                                       logger(DEBUG_CONNECTIONS, LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds", c->name, c->hostname, (long)mesh->loop.now.tv_sec - c->last_ping_time);
+                                       logger(mesh, MESHLINK_INFO, "%s (%s) didn't respond to PING in %ld seconds", c->name, c->hostname, (long)mesh->loop.now.tv_sec - c->last_ping_time);
                                } else if(c->last_ping_time + mesh->pinginterval <= mesh->loop.now.tv_sec) {
                                        send_ping(mesh, c);
                                        continue;
@@ -108,9 +120,9 @@ static void timeout_handler(event_loop_t *loop, void *data) {
                                }
                        } else {
                                if(c->status.connecting)
-                                       logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname);
+                                       logger(mesh, MESHLINK_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname);
                                else
-                                       logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname);
+                                       logger(mesh, MESHLINK_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname);
                        }
                        terminate_connection(mesh, c, c->status.active);
                }
@@ -119,6 +131,157 @@ static void timeout_handler(event_loop_t *loop, void *data) {
        timeout_set(&mesh->loop, data, &(struct timeval){mesh->pingtimeout, rand() % 100000});
 }
 
+// devclass asc, last_connect_try desc
+static int node_compare_devclass_asc_last_connect_try_desc(const void *a, const void *b)
+{
+       const node_t *na = a, *nb = b;
+
+       if(na->devclass < nb->devclass)
+               { return -1; }
+
+       if(na->devclass > nb->devclass)
+               { return 1; }
+
+       if(na->last_connect_try == nb->last_connect_try)
+               return 0;
+
+       if(nb->last_connect_try == 0 || na->last_connect_try < nb->last_connect_try)
+               return -1;
+
+       if(na->last_connect_try == 0 || na->last_connect_try > nb->last_connect_try)
+               return 1;
+
+       return 0;
+}
+
+// last_connect_try desc
+static int node_compare_last_connect_try_desc(const void *a, const void *b)
+{
+       const node_t *na = a, *nb = b;
+
+       if(na->last_connect_try == nb->last_connect_try)
+               return 0;
+
+       if(nb->last_connect_try == 0 || na->last_connect_try < nb->last_connect_try)
+               return -1;
+
+       if(na->last_connect_try == 0 || na->last_connect_try > nb->last_connect_try)
+               return 1;
+
+       return 0;
+}
+
+// devclass desc
+static int node_compare_devclass_desc(const void *a, const void *b)
+{
+       const node_t *na = a, *nb = b;
+
+       if(na->devclass < nb->devclass)
+               { return -1; }
+
+       if(na->devclass > nb->devclass)
+               { return 1; }
+
+       return 0;
+}
+
+
+/*
+
+
+autoconnect()
+{
+       timeout = 5
+
+       // find the best one for initial connect
+
+       if cur < min
+               newcon =
+                       first from nodes
+                               where dclass <= my.dclass and !connection and (timestamp - last_retry) > retry_timeout
+                               order by dclass asc, last_connection desc
+               if newcon
+                       timeout = 0
+                       goto connect
+
+
+       // find better nodes to connect to: in case we have less than min connections within [BACKBONE, i] and there are nodes which we are not connected to within the range
+
+       if min <= cur < max
+               j = 0
+               for i = BACKBONE to my.dclass
+                       j += count(from connections where node.dclass = i)
+                       if j < min
+                               newcon =
+                                       first from nodes
+                                               where dclass = i and !connection and (timestamp - last_retry) > retry_timeout
+                                               order by last_connection desc
+                               if newcon
+                                       goto connect
+                       else
+                               break
+
+
+       // heal partitions
+
+       if min <= cur < max
+               newcon =
+                       first from nodes
+                               where dclass <= my.dclass and !reachable and (timestamp - last_retry) > retry_timeout
+                               order by dclass asc, last_connection desc
+               if newcon
+                       goto connect
+
+
+       // connect
+
+connect:
+       if newcon
+               connect newcon
+
+
+       // disconnect outgoing connections in case we have more than min connections within [BACKBONE, i] and there are nodes which we are connected to within the range [i, PORTABLE]
+
+       if min < cur <= max
+               j = 0
+               for i = BACKBONE to my.dclass
+                       j += count(from connections where node.dclass = i)
+                       if min < j
+                               delcon =
+                                       first from nodes
+                                               where dclass >= i and outgoing_connection
+                                               order by dclass desc
+                               if disconnect
+                                       goto disconnect
+                               else
+                                       break
+
+
+       // disconnect connections in case we have more than enough connections
+
+       if max < cur
+               delcon =
+                       first from nodes
+                               where outgoing_connection
+                               order by dclass desc
+               goto disconnect
+
+       // disconnect
+
+disconnect
+       if delcon
+               disconnect delcon
+
+
+       // next iteration
+       next (timeout, autoconnect)
+
+}
+
+
+ */
+
+
 static void periodic_handler(event_loop_t *loop, void *data) {
        meshlink_handle_t *mesh = loop->data;
 
@@ -128,7 +291,7 @@ static void periodic_handler(event_loop_t *loop, void *data) {
        */
 
        if(mesh->contradicting_del_edge > 100 && mesh->contradicting_add_edge > 100) {
-               logger(DEBUG_ALWAYS, LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", mesh->sleeptime);
+               logger(mesh, MESHLINK_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", mesh->sleeptime);
                usleep(mesh->sleeptime * 1000000LL);
                mesh->sleeptime *= 2;
                if(mesh->sleeptime < 0)
@@ -142,157 +305,239 @@ static void periodic_handler(event_loop_t *loop, void *data) {
        mesh->contradicting_add_edge = 0;
        mesh->contradicting_del_edge = 0;
 
-       /* If AutoConnect is set, check if we need to make or break connections. */
+       int timeout = 5;
+
+       /* Check if we need to make or break connections. */
+
+       if(mesh->nodes->count > 1) {
+
+               logger(mesh, MESHLINK_INFO, "--- autoconnect begin ---");
+
+
+               int retry_timeout = min(mesh->nodes->count * 5, 60);
+
+               // connect disconnect nodes
+
+               node_t* connect_to = NULL;
+               node_t* disconnect_from = NULL;
+
+
+               // get cur_connects
+
+               int cur_connects = 0;
 
-       if(autoconnect && mesh->nodes->count > 1) {
-               /* Count number of active connections */
-               int nc = 0;
-               for list_each(connection_t, c, mesh->connections) {
-                       if(c->status.active)
-                               nc++;
+               for list_each(connection_t, c, mesh->connections)
+               {
+                       if(!c->status.remove_unused)
+                       {
+                               cur_connects += 1;
+                       }
                }
 
-               /* Count number of unreachable nodes */
-               int num_unreachable = 0;
-               for splay_each(node_t, n, mesh->nodes) {
-                       if(!n->status.reachable)
-                               num_unreachable++;
+               logger(mesh, MESHLINK_INFO, "* cur_connects = %d", cur_connects);
+
+
+               // get min_connects and max_connects
+
+               assert(mesh->devclass >= 0 && mesh->devclass <= _DEV_CLASS_MAX);
+
+               int min_connects = dev_class_traits[mesh->devclass].min_connects;
+               int max_connects = dev_class_traits[mesh->devclass].max_connects;
+
+               logger(mesh, MESHLINK_INFO, "* min_connects = %d", min_connects);
+               logger(mesh, MESHLINK_INFO, "* max_connects = %d", max_connects);
+
+
+               // find the best one for initial connect
+
+               if(cur_connects < min_connects)
+               {
+                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_last_connect_try_desc, NULL);
+
+                       for splay_each(node_t, n, mesh->nodes)
+                       {
+                               if(n->devclass <= mesh->devclass && !n->connection && (n->last_connect_try == 0 || (time(NULL) - n->last_connect_try) > retry_timeout))
+                                       { splay_insert(nodes, n); }
+                       }
+
+                       if(nodes->head)
+                       {
+                               logger(mesh, MESHLINK_INFO, "* found best one for initial connect");
+
+                               //timeout = 0;
+                               connect_to = (node_t*)nodes->head->data;
+                       }
+
+                       splay_free_tree(nodes);
                }
 
-               if(nc < autoconnect) {
-                       /* 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 r = rand() % mesh->nodes->count;
-                       int i = 0;
-
-                       for splay_each(node_t, n, mesh->nodes) {
-                               if(i++ != r)
-                                       continue;
 
-                               if(n->connection)
-                                       break;
+               // find better nodes to connect to
 
-                               bool found = false;
+               if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects)
+               {
+                       unsigned int connects = 0;
 
-                               for list_each(outgoing_t, outgoing, mesh->outgoings) {
-                                       if(!strcmp(outgoing->name, n->name)) {
-                                               found = true;
-                                               break;
-                                       }
+                       for(int devclass = 0; devclass <= mesh->devclass; ++devclass)
+                       {
+                               for list_each(connection_t, c, mesh->connections)
+                               {
+                                       if(!c->status.remove_unused && c->node && c->node->devclass == devclass)
+                                               { connects += 1; }
                                }
 
-                               if(!found) {
-                                       //TODO: if the node is blacklisted the connection will not happen, but
-                                       //the user will read this debug message "Autoconnecting to %s" that is misleading
-                                       logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
-                                       outgoing_t *outgoing = xzalloc(sizeof *outgoing);
-                                       outgoing->name = xstrdup(n->name);
-                                       list_insert_tail(mesh->outgoings, outgoing);
-                                       setup_outgoing_connection(mesh, outgoing);
-                               }
-                               break;
-                       }
-               } else if (num_unreachable > 0) {
-                       /* Min number of connections established. Now try
-                          to connect to some unreachable nodes to attempt
-                          to heal possible partitions.
-                       */
-                       int r = rand() % num_unreachable;
-                       int i = 0;
-
-                       for splay_each(node_t, n, mesh->nodes) {
-                               if(n->status.reachable)
-                                       continue;
-                               
-                               if(i++ != r)
-                                       continue;
+                               if( connects < min_connects )
+                               {
+                                       splay_tree_t *nodes = splay_alloc_tree(node_compare_last_connect_try_desc, NULL);
 
-                               if(n->connection)
-                                       break;
+                                       for splay_each(node_t, n, mesh->nodes)
+                                       {
+                                               if(n->devclass == devclass && !n->connection && (n->last_connect_try == 0 || (time(NULL) - n->last_connect_try) > retry_timeout))
+                                                       { splay_insert(nodes, n); }
+                                       }
 
-                               bool found = false;
+                                       if(nodes->head)
+                                       {
+                                               logger(mesh, MESHLINK_INFO, "* found better node");
+                                               connect_to = (node_t*)nodes->head->data;
 
-                               for list_each(outgoing_t, outgoing, mesh->outgoings) {
-                                       if(!strcmp(outgoing->name, n->name)) {
-                                               found = true;
+                                               splay_free_tree(nodes);
                                                break;
                                        }
-                               }
 
-                               if(!found) {
-                                       //TODO: if the node is blacklisted the connection will not happen, but
-                                       //the user will read this debug message "Autoconnecting to %s" that is misleading
-                                       logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name);
-                                       outgoing_t *outgoing = xzalloc(sizeof *outgoing);
-                                       outgoing->name = xstrdup(n->name);
-                                       list_insert_tail(mesh->outgoings, outgoing);
-                                       setup_outgoing_connection(mesh, outgoing);
+                                       splay_free_tree(nodes);
                                }
-                               break;
+                               else
+                                       { break; }
                        }
                }
-               if(nc > autoconnect) {
-                       /* 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, mesh->connections) {
-                               if(!c->status.active)
-                                       continue;
 
-                               if(i++ != r)
-                                       continue;
 
-                               if(!c->outgoing || !c->node || c->node->edge_tree->count < 2)
+               // heal partitions
+
+               if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects)
+               {
+                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_last_connect_try_desc, NULL);
+
+                       for splay_each(node_t, n, mesh->nodes)
+                       {
+                               if(n->devclass <= mesh->devclass && !n->status.reachable && (n->last_connect_try == 0 || (time(NULL) - n->last_connect_try) > retry_timeout))
+                                       { splay_insert(nodes, n); }
+                       }
+
+                       if(nodes->head)
+                       {
+                               logger(mesh, MESHLINK_INFO, "* try to heal partition");
+                               connect_to = (node_t*)nodes->head->data;
+                       }
+
+                       splay_free_tree(nodes);
+               }
+
+
+               // perform connect
+
+               if(connect_to && !connect_to->connection)
+               {
+                       /* check if there is already a connection attempt to this node */
+                       bool found = false;
+                       for list_each(outgoing_t, outgoing, mesh->outgoings) {
+                               if(!strcmp(outgoing->name, connect_to->name)) {
+                                       found = true;
                                        break;
+                               }
+                       }
 
-                               logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
-                               list_delete(mesh->outgoings, c->outgoing);
-                               c->outgoing = NULL;
-                               terminate_connection(mesh, c, c->status.active);
-                               break;
+                       if(!found)
+                       {
+                               logger(mesh, MESHLINK_INFO, "Autoconnecting to %s", connect_to->name);
+                               outgoing_t *outgoing = xzalloc(sizeof(outgoing_t));
+                               outgoing->mesh = mesh;
+                               outgoing->name = xstrdup(connect_to->name);
+                               list_insert_tail(mesh->outgoings, outgoing);
+                               setup_outgoing_connection(mesh, outgoing);      
                        }
                }
 
-               if(nc >= autoconnect) {
-                       /* If we have enough active connections,
-                          remove any pending outgoing connections.
-                          Do not remove pending connections to unreachable
-                          nodes.
-                       */
-                       node_t *o_node = NULL;
-                       for list_each(outgoing_t, o, mesh->outgoings) {
-                               o_node = lookup_node(mesh, o->name);
-                               /* o_node is NULL if it is not part of the graph yet */
-                               if(!o_node || !o_node->status.reachable)
-                                       continue;
 
-                               bool found = false;
-                               for list_each(connection_t, c, mesh->connections) {
-                                       if(c->outgoing == o) {
-                                               found = true;
-                                               break;
-                                       }
+               // disconnect suboptimal outgoing connections
+
+               if(min_connects < cur_connects && cur_connects <= max_connects)
+               {
+                       unsigned int connects = 0;
+
+                       for(int devclass = 0; devclass <= mesh->devclass; ++devclass)
+                       {
+                               for list_each(connection_t, c, mesh->connections)
+                               {
+                                       if(!c->status.remove_unused && c->node && c->node->devclass == devclass)
+                                               { connects += 1; }
                                }
-                               if(!found) {
-                                       logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->name);
-                                       /* The node variable is leaked in from using the list_each macro.
-                                          The o variable could be used, but using node directly
-                                          is more efficient.
-                                       */
-                                       list_delete_node(mesh->outgoings, node);
+
+                               if( min_connects < connects )
+                               {
+                                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL);
+
+                                       for list_each(connection_t, c, mesh->connections)
+                                       {
+                                               if(!c->status.remove_unused && c->outgoing && c->node && c->node->devclass >= devclass)
+                                                       { splay_insert(nodes, c->node); }
+                                       }
+
+                                       if(nodes->head)
+                                       {
+                                               logger(mesh, MESHLINK_INFO, "* disconnect suboptimal outgoing connection");
+                                               disconnect_from = (node_t*)nodes->head->data;
+                                       }
+
+                                       splay_free_tree(nodes);
+                                       break;
                                }
                        }
                }
+
+
+               // disconnect connections (too many connections)
+
+               if(!disconnect_from && max_connects < cur_connects)
+               {
+                       splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_desc, NULL);
+
+                       for list_each(connection_t, c, mesh->connections)
+                       {
+                               if(!c->status.remove_unused && c->node)
+                                       { splay_insert(nodes, c->node); }
+                       }
+
+                       if(nodes->head)
+                       {
+                               logger(mesh, MESHLINK_INFO, "* disconnect connection (too many connections");
+
+                               //timeout = 0;
+                               disconnect_from = (node_t*)nodes->head->data;
+                       }
+
+                       splay_free_tree(nodes);
+               }
+
+
+               // perform disconnect
+
+               if(disconnect_from && disconnect_from->connection)
+               {
+                       logger(mesh, MESHLINK_INFO, "Autodisconnecting from %s", disconnect_from->connection->name);
+                       list_delete(mesh->outgoings, disconnect_from->connection->outgoing);
+                       disconnect_from->connection->outgoing = NULL;
+                       terminate_connection(mesh, disconnect_from->connection, disconnect_from->connection->status.active);
+               }
+
+
+               // done!
+
+               logger(mesh, MESHLINK_INFO, "--- autoconnect end ---");
        }
 
-       timeout_set(&mesh->loop, data, &(struct timeval){5, rand() % 100000});
+       timeout_set(&mesh->loop, data, &(struct timeval){timeout, rand() % 100000});
 }
 
 void handle_meta_connection_data(meshlink_handle_t *mesh, connection_t *c) {
@@ -325,14 +570,14 @@ void retry(meshlink_handle_t *mesh) {
 */
 int main_loop(meshlink_handle_t *mesh) {
        timeout_add(&mesh->loop, &mesh->pingtimer, timeout_handler, &mesh->pingtimer, &(struct timeval){mesh->pingtimeout, rand() % 100000});
-       timeout_add(&mesh->loop, &mesh->periodictimer, periodic_handler, &mesh->periodictimer, &(struct timeval){mesh->pingtimeout, rand() % 100000});
+       timeout_add(&mesh->loop, &mesh->periodictimer, periodic_handler, &mesh->periodictimer, &(struct timeval){0, 0});
 
        //Add signal handler
        mesh->datafromapp.signum = 0;
        signal_add(&(mesh->loop),&(mesh->datafromapp), (signal_cb_t)meshlink_send_from_queue,mesh, mesh->datafromapp.signum);
 
-       if(!event_loop_run(&mesh->loop)) {
-               logger(DEBUG_ALWAYS, LOG_ERR, "Error while waiting for input: %s", strerror(errno));
+       if(!event_loop_run(&(mesh->loop), &(mesh->mesh_mutex))) {
+               logger(mesh, MESHLINK_ERROR, "Error while waiting for input: %s", strerror(errno));
                return 1;
        }