From 42cef01039177f3c762001439c8fb6bbdcd6e159 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 1 Dec 2019 23:56:10 +0100 Subject: [PATCH] Add meshlink_get_all_nodes_by_last_reachable(). MeshLink now keeps track of when a node was last reachable. This can be used by an application to detect nodes that were never reachable or which have not been reachable for a certain amount of time. --- src/graph.c | 4 +- src/meshlink.c | 46 +++++++++ src/meshlink.h | 24 +++++ src/meshlink.sym | 1 + src/meshlink_internal.h | 4 +- src/net_setup.c | 18 +++- src/node.h | 4 +- test/Makefile.am | 5 + test/get-all-nodes.c | 216 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 317 insertions(+), 5 deletions(-) create mode 100644 test/get-all-nodes.c diff --git a/src/graph.c b/src/graph.c index d40087d8..5afa7c32 100644 --- a/src/graph.c +++ b/src/graph.c @@ -165,12 +165,14 @@ static void check_reachability(meshlink_handle_t *mesh) { if(n->status.visited != n->status.reachable) { n->status.reachable = !n->status.reachable; - n->last_state_change = mesh->loop.now.tv_sec; + n->status.dirty = true; if(n->status.reachable) { logger(mesh, MESHLINK_DEBUG, "Node %s became reachable", n->name); + n->last_reachable = mesh->loop.now.tv_sec; } else { logger(mesh, MESHLINK_DEBUG, "Node %s became unreachable", n->name); + n->last_unreachable = mesh->loop.now.tv_sec; } /* TODO: only clear status.validkey if node is unreachable? */ diff --git a/src/meshlink.c b/src/meshlink.c index f30fcdf3..88a4c319 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -1501,6 +1501,8 @@ bool meshlink_start(meshlink_handle_t *mesh) { pthread_cond_wait(&mesh->cond, &mesh->mutex); mesh->threadstarted = true; + mesh->self->last_reachable = time(NULL); + mesh->self->status.dirty = true; pthread_mutex_unlock(&mesh->mutex); return true; @@ -1515,6 +1517,11 @@ void meshlink_stop(meshlink_handle_t *mesh) { pthread_mutex_lock(&mesh->mutex); logger(mesh, MESHLINK_DEBUG, "meshlink_stop called\n"); + if(mesh->self) { + mesh->self->last_unreachable = time(NULL); + mesh->self->status.dirty = true; + } + // Shut down the main thread event_loop_stop(&mesh->loop); @@ -2025,6 +2032,31 @@ static bool search_node_by_submesh(const node_t *node, const void *condition) { return false; } +struct time_range { + time_t start; + time_t end; +}; + +static bool search_node_by_last_reachable(const node_t *node, const void *condition) { + const struct time_range *range = condition; + time_t start = node->last_reachable; + time_t end = node->last_unreachable; + + if(end < start) { + end = time(NULL); + + if(end < start) { + start = end; + } + } + + if(range->end >= range->start) { + return start <= range->end && end >= range->start; + } else { + return start > range->start || end < range->end; + } +} + meshlink_node_t **meshlink_get_all_nodes_by_dev_class(meshlink_handle_t *mesh, dev_class_t devclass, meshlink_node_t **nodes, size_t *nmemb) { if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT || !nmemb) { meshlink_errno = MESHLINK_EINVAL; @@ -2043,6 +2075,17 @@ meshlink_node_t **meshlink_get_all_nodes_by_submesh(meshlink_handle_t *mesh, mes return meshlink_get_all_nodes_by_condition(mesh, submesh, nodes, nmemb, search_node_by_submesh); } +meshlink_node_t **meshlink_get_all_nodes_by_last_reachable(meshlink_handle_t *mesh, time_t start, time_t end, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || !nmemb) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + struct time_range range = {start, end}; + + return meshlink_get_all_nodes_by_condition(mesh, &range, nodes, nmemb, search_node_by_last_reachable); +} + dev_class_t meshlink_get_node_dev_class(meshlink_handle_t *mesh, meshlink_node_t *node) { if(!mesh || !node) { meshlink_errno = MESHLINK_EINVAL; @@ -2714,6 +2757,9 @@ char *meshlink_export(meshlink_handle_t *mesh) { packmsg_add_sockaddr(&out, &mesh->self->recent[i]); } + packmsg_add_int64(&out, 0); + packmsg_add_int64(&out, 0); + pthread_mutex_unlock(&mesh->mutex); if(!packmsg_output_ok(&out)) { diff --git a/src/meshlink.h b/src/meshlink.h index 3298e7c4..98a34bd6 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -712,6 +712,30 @@ extern struct meshlink_node **meshlink_get_all_nodes_by_dev_class(struct meshlin */ extern struct meshlink_node **meshlink_get_all_nodes_by_submesh(struct meshlink_handle *mesh, struct meshlink_submesh *submesh, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); +/// Get the list of all nodes by time they were last reachable. +/** This function returns a list with handles for all the nodes whose last known reachability time overlaps with the given time range. + * If the range includes 0, it will count nodes that were never online. + * If start is bigger than end, the result will be inverted. + * + * \memberof meshlink_handle + * @param mesh A handle which represents an instance of MeshLink. + * @param start Start time. + * @param end End time. + * @param nodes A pointer to a previously allocated array of pointers to struct meshlink_node, or NULL in which case MeshLink will allocate a new array. + * The application can supply an array it allocated itself with malloc, or the return value from the previous call to this function (which is the preferred way). + * The application is allowed to call free() on the array whenever it wishes. + * The pointers in the array are valid until meshlink_close() is called. + * @param nmemb A pointer to a variable holding the number of nodes that were reachable within the period given by @a start and @a end. + * In case the @a nodes argument is not NULL, MeshLink might call realloc() on the array to change its size. + * The contents of this variable will be changed to reflect the new size of the array. + * + * @return A pointer to an array containing pointers to all known nodes that were reachable within the period given by @a start and @a end. + * If the @a nodes argument was not NULL, then the return value can either be the same value or a different value. + * If it is a new value, the old value of @a nodes should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. + */ +extern struct meshlink_node **meshlink_get_all_nodes_by_last_reachable(struct meshlink_handle *mesh, time_t start, time_t end, struct meshlink_node **nodes, size_t *nmemb) __attribute__((__warn_unused_result__)); + /// Get the node's device class. /** This function returns the device class of the given node. * diff --git a/src/meshlink.sym b/src/meshlink.sym index da5f0bc4..674a260e 100644 --- a/src/meshlink.sym +++ b/src/meshlink.sym @@ -31,6 +31,7 @@ meshlink_export meshlink_forget_node meshlink_get_all_nodes meshlink_get_all_nodes_by_dev_class +meshlink_get_all_nodes_by_last_reachable meshlink_get_all_nodes_by_submesh meshlink_get_external_address meshlink_get_external_address_for_family diff --git a/src/meshlink_internal.h b/src/meshlink_internal.h index 5f87632c..15edba9a 100644 --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@ -42,8 +42,8 @@ static const char meshlink_invitation_label[] = "MeshLink invitation"; static const char meshlink_tcp_label[] = "MeshLink TCP"; static const char meshlink_udp_label[] = "MeshLink UDP"; -#define MESHLINK_CONFIG_VERSION 1 -#define MESHLINK_INVITATION_VERSION 1 +#define MESHLINK_CONFIG_VERSION 2 +#define MESHLINK_INVITATION_VERSION 2 struct CattaServer; struct CattaSServiceBrowser; diff --git a/src/net_setup.c b/src/net_setup.c index ec16b400..8c8ef6ed 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -116,6 +116,15 @@ bool node_read_public_key(meshlink_handle_t *mesh, node_t *n) { } } + time_t last_reachable = packmsg_get_int64(&in); + time_t last_unreachable = packmsg_get_int64(&in); + + if(!n->last_reachable) { + n->last_reachable = last_reachable; + } + + if(!n->last_unreachable) { + n->last_unreachable = last_unreachable; } config_free(&config); @@ -192,6 +201,9 @@ bool node_read_from_config(meshlink_handle_t *mesh, node_t *n, const config_t *c } } + n->last_reachable = packmsg_get_int64(&in); + n->last_unreachable = packmsg_get_int64(&in); + return packmsg_done(&in); } @@ -233,6 +245,9 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n) { packmsg_add_sockaddr(&out, &n->recent[i]); } + packmsg_add_int64(&out, n->last_reachable); + packmsg_add_int64(&out, n->last_unreachable); + if(!packmsg_output_ok(&out)) { meshlink_errno = MESHLINK_EINTERNAL; return false; @@ -245,6 +260,8 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n) { return false; } + + return true; } @@ -398,7 +415,6 @@ bool setup_myself(meshlink_handle_t *mesh) { mesh->self->nexthop = mesh->self; mesh->self->status.reachable = true; - mesh->self->last_state_change = mesh->loop.now.tv_sec; node_add(mesh, mesh->self); diff --git a/src/node.h b/src/node.h index 1a2fdca0..19e31fac 100644 --- a/src/node.h +++ b/src/node.h @@ -76,7 +76,6 @@ typedef struct node_t { struct meshlink_handle *mesh; /* The mesh this node belongs to */ struct submesh_t *submesh; /* Nodes Sub-Mesh Handle*/ - time_t last_state_change; time_t last_req_key; struct ecdsa *ecdsa; /* His public ECDSA key */ @@ -89,6 +88,9 @@ typedef struct node_t { sockaddr_t recent[MAX_RECENT]; /* Recently seen addresses */ // Graph-related member variables + time_t last_reachable; + time_t last_unreachable; + int distance; struct node_t *nexthop; /* nearest node from us to him */ struct edge_t *prevedge; /* nearest node from him to us */ diff --git a/test/Makefile.am b/test/Makefile.am index 1a0f8710..f631ea50 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -13,6 +13,7 @@ TESTS = \ duplicate \ encrypted \ ephemeral \ + get-all-nodes \ import-export \ invite-join \ sign-verify \ @@ -44,6 +45,7 @@ check_PROGRAMS = \ echo-fork \ encrypted \ ephemeral \ + get-all-nodes \ import-export \ invite-join \ sign-verify \ @@ -99,6 +101,9 @@ encrypted_LDADD = ../src/libmeshlink.la ephemeral_SOURCES = ephemeral.c utils.c utils.h ephemeral_LDADD = ../src/libmeshlink.la +get_all_nodes_SOURCES = get-all-nodes.c utils.c utils.h +get_all_nodes_LDADD = ../src/libmeshlink.la + import_export_SOURCES = import-export.c utils.c utils.h import_export_LDADD = ../src/libmeshlink.la diff --git a/test/get-all-nodes.c b/test/get-all-nodes.c new file mode 100644 index 00000000..5dc4af1e --- /dev/null +++ b/test/get-all-nodes.c @@ -0,0 +1,216 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "meshlink.h" +#include "utils.h" + +struct sync_flag bar_reachable; + +void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { + (void)mesh; + + if(reachable && !strcmp(node->name, "bar")) { + set_sync_flag(&bar_reachable, true); + } +} + +int main() { + struct meshlink_node **nodes = NULL; + size_t nnodes = 0; + + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open new meshlink instances. + + assert(meshlink_destroy("get_all_nodes_conf.1")); + assert(meshlink_destroy("get_all_nodes_conf.2")); + assert(meshlink_destroy("get_all_nodes_conf.3")); + + meshlink_handle_t *mesh[3]; + mesh[0] = meshlink_open("get_all_nodes_conf.1", "foo", "import-export", DEV_CLASS_BACKBONE); + assert(mesh[0]); + + mesh[1] = meshlink_open("get_all_nodes_conf.2", "bar", "import-export", DEV_CLASS_STATIONARY); + assert(mesh[1]); + + mesh[2] = meshlink_open("get_all_nodes_conf.3", "baz", "get-all-nodes", DEV_CLASS_STATIONARY); + assert(mesh[2]); + + // Check that we only know about ourself. + + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + // We should never have been online. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + // Let nodes know about each other. + + for(int i = 0; i < 3; i++) { + meshlink_enable_discovery(mesh[i], false); + assert(meshlink_add_address(mesh[i], "localhost")); + char *data = meshlink_export(mesh[i]); + assert(data); + + for(int j = 0; j < 3; j++) { + if(i == j) { + continue; + } + + assert(meshlink_import(mesh[j], data)); + } + } + + // We should know about all nodes now, and their device class. + + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + assert(nnodes == 3); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_BACKBONE, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_STATIONARY, nodes, &nnodes); + assert(nnodes == 2); + + // But no node should have been online. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 3); + + // Start foo. + + time_t foo_started = time(NULL); + assert(meshlink_start(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], foo_started - 1, -1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started - 1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started + 1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + // Start bar and wait for it to connect. + + meshlink_set_node_status_cb(mesh[0], status_cb); + + sleep(2); + assert(meshlink_start(mesh[1])); + assert(wait_sync_flag(&bar_reachable, 20)); + time_t bar_started = time(NULL); + + // Validate time ranges. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_node(mesh[0], "baz")); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started + 1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_started, bar_started, nodes, &nnodes); + assert(nnodes == 2); + assert(nodes[0] == meshlink_get_node(mesh[0], "bar")); + assert(nodes[1] == meshlink_get_self(mesh[0])); + + // Stop bar. + + meshlink_stop(mesh[1]); + sleep(2); + time_t bar_stopped = time(NULL); + + // Validate we can see when bar was reachable. + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_stopped, bar_stopped, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_started, bar_started, nodes, &nnodes); + assert(nnodes == 2); + assert(nodes[0] == meshlink_get_node(mesh[0], "bar")); + assert(nodes[1] == meshlink_get_self(mesh[0])); + + // Close and restart foo, check that it remembers correctly. + + meshlink_close(mesh[0]); + sleep(2); + time_t foo_stopped = time(NULL); + mesh[0] = meshlink_open("get_all_nodes_conf.1", "foo", "import-export", DEV_CLASS_BACKBONE); + assert(mesh[0]); + + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + assert(nnodes == 3); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_BACKBONE, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_dev_class(mesh[0], DEV_CLASS_STATIONARY, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, 0, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_node(mesh[0], "baz")); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 0, -1, nodes, &nnodes); + assert(nnodes == 2); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started - 1, nodes, &nnodes); + assert(nnodes == 0); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], 1, foo_started + 1, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_started, bar_started, nodes, &nnodes); + assert(nnodes == 2); + assert(nodes[0] == meshlink_get_node(mesh[0], "bar")); + assert(nodes[1] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], bar_stopped, bar_stopped, nodes, &nnodes); + assert(nnodes == 1); + assert(nodes[0] == meshlink_get_self(mesh[0])); + + nodes = meshlink_get_all_nodes_by_last_reachable(mesh[0], foo_stopped, -1, nodes, &nnodes); + assert(nnodes == 0); + + // Clean up. + + for(int i = 0; i < 3; i++) { + meshlink_close(mesh[i]); + } +} -- 2.39.2