From: Guus Sliepen Date: Fri, 22 Aug 2014 08:58:47 +0000 (+0200) Subject: Merge branch 'channels' X-Git-Url: http://git.meshlink.io/?p=meshlink;a=commitdiff_plain;h=01eaeb3c9fa60ae9c6e5b866acd9baef79622d99;hp=d4f1bfacd6e7add63982692e89af39ec66c24b60 Merge branch 'channels' Conflicts: .gitmodules src/node.h --- diff --git a/.gitmodules b/.gitmodules index add1976b..e938b091 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ +[submodule "avahi"] + path = avahi + url = git@chicago.everbase.net:meshlink/avahi-noptr.git + branch = noptr [submodule "src/utcp"] path = src/utcp url = git://meshlink.io/utcp diff --git a/README.avahi b/README.avahi new file mode 100644 index 00000000..81d545f8 --- /dev/null +++ b/README.avahi @@ -0,0 +1,5 @@ + +cd avahi +./bootstrap.sh +./configure --disable-nls --disable-glib --disable-gobject --disable-qt3 --disable-qt4 --disable-gtk --disable-gtk3 --disable-dbus --disable-gdbm --disable-libdaemon --disable-python --disable-pygtk --disable-python-dbus --disable-mono --disable-monodoc --disable-autoipd --disable-doxygen-doc --disable-doxygen-dot --disable-doxygen-xml --disable-doxygen-html --disable-manpages --disable-xmltoman CFLAGS=-fPIC +make diff --git a/avahi b/avahi new file mode 160000 index 00000000..7a5b2f69 --- /dev/null +++ b/avahi @@ -0,0 +1 @@ +Subproject commit 7a5b2f69af7d36d6cd4153142f125fa011784e03 diff --git a/configure.ac b/configure.ac index 2f1caa68..6c6543cb 100644 --- a/configure.ac +++ b/configure.ac @@ -99,6 +99,10 @@ AC_CHECK_TYPES([socklen_t, struct addrinfo, struct sockaddr_in6], , , [#include "src/have.h"] ) +AC_CHECK_TYPES([struct sockaddr_storage], ,AC_MSG_ERROR([System must support struct sockaddr_storage.]), + [#include "src/have.h"] +) + dnl Checks for library functions. AC_TYPE_SIGNAL AC_CHECK_FUNCS([asprintf fchmod fork get_current_dir_name gettimeofday random select strdup strerror time usleep], diff --git a/examples/Makefile.am b/examples/Makefile.am index af8f0f45..9be95b13 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,4 +1,4 @@ -bin_PROGRAMS = meshlinkapp chat chatpp +bin_PROGRAMS = meshlinkapp chat chatpp manynodes AM_CPPFLAGS = -I../src @@ -10,3 +10,6 @@ chat_LDADD = ../src/libmeshlink.la chatpp_SOURCES = chatpp.cc chatpp_LDADD = ../src/libmeshlink.la + +manynodes_SOURCES = manynodes.cc +manynodes_LDADD = ../src/libmeshlink.la diff --git a/examples/chat.c b/examples/chat.c index 70d17d7d..c6d67fb5 100644 --- a/examples/chat.c +++ b/examples/chat.c @@ -5,8 +5,14 @@ #include "../src/meshlink.h" static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { - const char *levelstr[] = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}; - fprintf(stderr, "%s: %s\n", levelstr[level], text); + const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text); } static void receive(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { @@ -27,6 +33,9 @@ static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool rea printf("%s left.\n", node->name); } +static meshlink_node_t **nodes; +static size_t nnodes; + static void parse_command(meshlink_handle_t *mesh, char *buf) { char *arg = strchr(buf, ' '); if(arg) @@ -42,7 +51,7 @@ static void parse_command(meshlink_handle_t *mesh, char *buf) { invitation = meshlink_invite(mesh, arg); if(!invitation) { - fprintf(stderr, "Could not invite '%s': %s\n", arg, mesh->errstr); + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); return; } @@ -53,11 +62,16 @@ static void parse_command(meshlink_handle_t *mesh, char *buf) { fprintf(stderr, "/join requires an argument!\n"); return; } - + meshlink_stop(mesh); if(!meshlink_join(mesh, arg)) - fprintf(stderr, "Could not join using invitation: %s\n", mesh->errstr); - else + fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno)); + else { fprintf(stderr, "Invitation accepted!\n"); + if(!meshlink_start(mesh)) { + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno)); + return; + } + } } else if(!strcasecmp(buf, "kick")) { if(!arg) { fprintf(stderr, "/kick requires an argument!\n"); @@ -66,7 +80,7 @@ static void parse_command(meshlink_handle_t *mesh, char *buf) { meshlink_node_t *node = meshlink_get_node(mesh, arg); if(!node) { - fprintf(stderr, "Unknown node '%s'\n", arg); + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); return; } @@ -75,22 +89,19 @@ static void parse_command(meshlink_handle_t *mesh, char *buf) { printf("Node '%s' blacklisted.\n", arg); } else if(!strcasecmp(buf, "who")) { if(!arg) { - meshlink_node_t *nodes[100]; - size_t n = meshlink_get_all_nodes(mesh, nodes, 100); - if(!n) { - fprintf(stderr, "No nodes known!\n"); + nodes = meshlink_get_all_nodes(mesh, nodes, &nnodes); + if(!nnodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); } else { - printf("Known nodes:"); - for(int i = 0; i < n && i < 100; i++) + printf("%zu known nodes:", nnodes); + for(int i = 0; i < nnodes; i++) printf(" %s", nodes[i]->name); - if(n > 100) - printf(" (and %zu more)", n - 100); printf("\n"); } } else { meshlink_node_t *node = meshlink_get_node(mesh, arg); if(!node) { - fprintf(stderr, "Unknown node '%s'\n", arg); + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); } else { printf("Node %s found\n", arg); } @@ -153,7 +164,7 @@ static void parse_input(meshlink_handle_t *mesh, char *buf) { destination = meshlink_get_node(mesh, buf); if(!destination) { - fprintf(stderr, "Unknown node '%s'\n", buf); + fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink_strerror(meshlink_errno)); return; } } @@ -164,7 +175,7 @@ static void parse_input(meshlink_handle_t *mesh, char *buf) { } if(!meshlink_send(mesh, destination, msg, strlen(msg) + 1)) { - fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, mesh->errstr); + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); return; } @@ -182,9 +193,11 @@ int main(int argc, char *argv[]) { if(argc > 2) nick = argv[2]; - meshlink_handle_t *mesh = meshlink_open(confbase, nick); + meshlink_set_log_cb(NULL, MESHLINK_INFO, log_message); + + meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY); if(!mesh) { - fprintf(stderr, "Could not open MeshLink!\n"); + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); return 1; } @@ -193,7 +206,7 @@ int main(int argc, char *argv[]) { meshlink_set_log_cb(mesh, MESHLINK_INFO, log_message); if(!meshlink_start(mesh)) { - fprintf(stderr, "Could not start MeshLink: %s\n", mesh->errstr); + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno)); return 1; } diff --git a/examples/chatpp.cc b/examples/chatpp.cc index 864a3e80..331f6584 100644 --- a/examples/chatpp.cc +++ b/examples/chatpp.cc @@ -4,28 +4,35 @@ #include #include "../src/meshlink++.h" -static void log_message(meshlink::mesh *mesh, meshlink::log_level_t level, const char *text) { - const char *levelstr[] = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}; - fprintf(stderr, "%s: %s\n", levelstr[level], text); -} - -static void receive(meshlink::mesh *mesh, meshlink::node *source, const void *data, size_t len) { - const char *msg = (const char *)data; +class ChatMesh : public meshlink::mesh +{ +public: + void log(meshlink::log_level_t level, const char *text) { + const char *levelstr[] = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}; + fprintf(stderr, "%s: %s\n", levelstr[level], text); + } - if(!len || msg[len - 1]) { - fprintf(stderr, "Received invalid data from %s\n", source->name); - return; + void receive(meshlink::node *source, const void *data, size_t len) { + const char *msg = (const char *)data; + + if(!len || msg[len - 1]) { + fprintf(stderr, "Received invalid data from %s\n", source->name); + return; + } + + printf("%s says: %s\n", source->name, msg); } - printf("%s says: %s\n", source->name, msg); -} + void node_status(meshlink::node *node, bool reachable) { + if(reachable) + printf("%s joined.\n", node->name); + else + printf("%s left.\n", node->name); + } +}; -static void node_status(meshlink::mesh *mesh, meshlink::node *node, bool reachable) { - if(reachable) - printf("%s joined.\n", node->name); - else - printf("%s left.\n", node->name); -} +static meshlink::node **nodes; +static size_t nnodes; static void parse_command(meshlink::mesh *mesh, char *buf) { char *arg = strchr(buf, ' '); @@ -42,7 +49,7 @@ static void parse_command(meshlink::mesh *mesh, char *buf) { invitation = mesh->invite(arg); if(!invitation) { - fprintf(stderr, "Could not invite '%s': %s\n", arg, mesh->errstr); + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink::strerror()); return; } @@ -55,7 +62,7 @@ static void parse_command(meshlink::mesh *mesh, char *buf) { } if(!mesh->join(arg)) - fprintf(stderr, "Could not join using invitation: %s\n", mesh->errstr); + fprintf(stderr, "Could not join using invitation: %s\n", meshlink::strerror()); else fprintf(stderr, "Invitation accepted!\n"); } else if(!strcasecmp(buf, "kick")) { @@ -66,7 +73,7 @@ static void parse_command(meshlink::mesh *mesh, char *buf) { meshlink::node *node = mesh->get_node(arg); if(!node) { - fprintf(stderr, "Unknown node '%s'\n", arg); + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink::strerror()); return; } @@ -75,22 +82,19 @@ static void parse_command(meshlink::mesh *mesh, char *buf) { printf("Node '%s' blacklisted.\n", arg); } else if(!strcasecmp(buf, "who")) { if(!arg) { - meshlink::node *nodes[100]; - size_t n = mesh->get_all_nodes(nodes, 100); - if(!n) { - fprintf(stderr, "No nodes known!\n"); + nodes = mesh->get_all_nodes(nodes, &nnodes); + if(!nodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink::strerror()); } else { - printf("Known nodes:"); - for(int i = 0; i < n && i < 100; i++) + printf("%zu known nodes:", nnodes); + for(size_t i = 0; i < nnodes; i++) printf(" %s", nodes[i]->name); - if(n > 100) - printf(" (and %zu more)", n - 100); printf("\n"); } } else { meshlink::node *node = mesh->get_node(arg); if(!node) { - fprintf(stderr, "Unknown node '%s'\n", arg); + fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink::strerror()); } else { printf("Node %s found\n", arg); } @@ -153,7 +157,7 @@ static void parse_input(meshlink::mesh *mesh, char *buf) { destination = mesh->get_node(buf); if(!destination) { - fprintf(stderr, "Unknown node '%s'\n", buf); + fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink::strerror()); return; } } @@ -164,13 +168,14 @@ static void parse_input(meshlink::mesh *mesh, char *buf) { } if(!mesh->send(destination, msg, strlen(msg) + 1)) { - fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, mesh->errstr); + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink::strerror()); return; } printf("Message sent to '%s'.\n", destination->name); } + int main(int argc, char *argv[]) { const char *confbase = ".chat"; const char *nick = NULL; @@ -182,18 +187,15 @@ int main(int argc, char *argv[]) { if(argc > 2) nick = argv[2]; - meshlink::mesh *mesh = meshlink::open(confbase, nick); + ChatMesh* mesh = meshlink::open(confbase, nick, "chatpp", DEV_CLASS_STATIONARY); + if(!mesh) { - fprintf(stderr, "Could not open MeshLink!\n"); + fprintf(stderr, "Could not open MeshLink: %s\n", meshlink::strerror()); return 1; } - mesh->set_receive_cb(receive); - mesh->set_node_status_cb(node_status); - mesh->set_log_cb(MESHLINK_INFO, log_message); - if(!mesh->start()) { - fprintf(stderr, "Could not start MeshLink: %s\n", mesh->errstr); + fprintf(stderr, "Could not start MeshLink: %s\n", meshlink::strerror()); return 1; } diff --git a/examples/manynodes.c b/examples/manynodes.c new file mode 100644 index 00000000..7b23fc51 --- /dev/null +++ b/examples/manynodes.c @@ -0,0 +1,415 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../src/meshlink.h" +#include "../src/devtools.h" + +#include +#include +#include + +#include +#include + +static int n = 10; +static meshlink_handle_t **mesh; + +static int nodeindex = 0; + +static meshlink_node_t **nodes; +static size_t nnodes; + +static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + fprintf(stderr, "%s\t%s:\x1b[0m %s\n", mesh ? mesh->name : "global",levelstr[level], text); +} + +//Test mesh sending data +static void testmesh () { + + for(int nindex = 0; nindex < n; nindex++) { + + nodes = meshlink_get_all_nodes(mesh[nindex], nodes, &nnodes); + if(!nodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + } else { + printf("%zu known nodes:\n", nnodes); + for(int i = 0; i < nnodes; i++) { + //printf(" %s\n", nodes[i]->name); + if(!meshlink_send(mesh[nindex], nodes[i], "magic", strlen("magic") + 1)) { + fprintf(stderr, "Could not send message to '%s': %s\n", nodes[i]->name, meshlink_strerror(meshlink_errno)); + } + } + + } + + } +} +// Make all nodes know about each other by importing each others public keys and addresses. +static void linkmesh() { + for(int i = 0; i < n; i++) { + char *datai = meshlink_export(mesh[i]); + + for(int j = i + 1; j < n; j++) { + char *dataj = meshlink_export(mesh[j]); + meshlink_import(mesh[i], dataj); + meshlink_import(mesh[j], datai); + free(dataj); + } + + free(datai); + } +} + +static bool exportmeshgraph(const char* path) +{ + struct stat ps; + int psr = stat(path, &ps); + + if(psr == 0 || errno != ENOENT) + { + if(psr == -1) + { perror("stat"); } + else + { fprintf(stderr, "%s exists already\n", path); } + + return false; + } + + FILE* stream = fopen(path, "w"); + + if(!stream) + { + perror("stream"); + return false; + } + + if(!devtool_export_json_all_edges_state(mesh[nodeindex], stream)) + { + fclose(stream); + fprintf(stderr, "could not export graph\n"); + return false; + } + + fclose(stream); + return true; +} + + +void exportmeshgraph_timer(int signum) +{ + struct timeval ts; + gettimeofday(&ts, NULL); + + char name[1024]; + snprintf(name, sizeof(name), "graph_%ld_%03ld.json", ts.tv_sec, ts.tv_usec/1000); + + exportmeshgraph(name); +} + +static bool exportmeshgraph_started = false; + +static bool exportmeshgraph_end(const char* none) +{ + if(!exportmeshgraph_started) + { return false; } + + struct itimerval zero_timer = { 0 }; + setitimer (ITIMER_REAL, &zero_timer, NULL); + + exportmeshgraph_started = false; + + return true; +} + +static bool exportmeshgraph_begin(const char* timeout_str) +{ + if(!timeout_str) + return false; + + if(exportmeshgraph_started) + { + if(!exportmeshgraph_end(NULL)) + return false; + } + + // get timeout + int timeout = atoi(timeout_str); + + if(timeout < 100) + { timeout = 100; } + + int timeout_sec = timeout / 1000; + int timeout_msec = timeout % 1000; + + /* Install timer_handler as the signal handler for SIGALRM. */ + signal(SIGALRM, exportmeshgraph_timer); + + /* Configure the timer to expire immediately... */ + struct itimerval timer; + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 1000; + + /* ... and every X msec after that. */ + timer.it_interval.tv_sec = timeout_sec; + timer.it_interval.tv_usec = timeout_msec * 1000; + + /* Start a real timer. */ + setitimer (ITIMER_REAL, &timer, NULL); + + exportmeshgraph_started = true; + + return true; +} + +static void parse_command(char *buf) { + char *arg = strchr(buf, ' '); + if(arg) + *arg++ = 0; + + if(!strcasecmp(buf, "invite")) { + char *invitation; + + if(!arg) { + fprintf(stderr, "/invite requires an argument!\n"); + return; + } + + invitation = meshlink_invite(mesh[nodeindex], arg); + if(!invitation) { + fprintf(stderr, "Could not invite '%s': %s\n", arg, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Invitation for %s: %s\n", arg, invitation); + free(invitation); + } else if(!strcasecmp(buf, "join")) { + if(!arg) { + fprintf(stderr, "/join requires an argument!\n"); + return; + } + meshlink_stop(mesh[nodeindex]); + if(!meshlink_join(mesh[nodeindex], arg)) + fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno)); + else { + fprintf(stderr, "Invitation accepted!\n"); + meshlink_start(mesh[nodeindex]); + } + } else if(!strcasecmp(buf, "kick")) { + if(!arg) { + fprintf(stderr, "/kick requires an argument!\n"); + return; + } + + meshlink_node_t *node = meshlink_get_node(mesh[nodeindex], arg); + if(!node) { + fprintf(stderr, "Unknown node '%s'\n", arg); + return; + } + + meshlink_blacklist(mesh[nodeindex], node); + + printf("Node '%s' blacklisted.\n", arg); + } else if(!strcasecmp(buf, "who")) { + if(!arg) { + nodes = meshlink_get_all_nodes(mesh[nodeindex], nodes, &nnodes); + if(!nodes) { + fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno)); + } else { + printf("%zu known nodes:", nnodes); + for(int i = 0; i < nnodes; i++) + printf(" %s", nodes[i]->name); + printf("\n"); + } + } else { + meshlink_node_t *node = meshlink_get_node(mesh[nodeindex], arg); + if(!node) { + fprintf(stderr, "Unknown node '%s'\n", arg); + } else { + printf("Node %s found, pmtu %zd\n", arg, meshlink_get_pmtu(mesh[nodeindex], node)); + } + } + } else if(!strcasecmp(buf, "link")) { + linkmesh(); + } else if(!strcasecmp(buf, "eg")) { + exportmeshgraph(arg); + } else if(!strcasecmp(buf, "egb")) { + exportmeshgraph_begin(arg); + } else if(!strcasecmp(buf, "ege")) { + exportmeshgraph_end(NULL); + } else if(!strcasecmp(buf, "test")) { + testmesh(); + } else if(!strcasecmp(buf, "select")) { + if(!arg) { + fprintf(stderr, "/select requires an argument!\n"); + return; + } + nodeindex = atoi(arg); + printf("Index is now %d\n",nodeindex); + + } else if(!strcasecmp(buf, "quit")) { + printf("Bye!\n"); + fclose(stdin); + } else if(!strcasecmp(buf, "help")) { + printf( + ": Send a message to the given node.\n" + " Subsequent messages don't need the : prefix.\n" + "/invite Create an invitation for a new node.\n" + "/join Join an existing mesh using an invitation.\n" + "/kick Blacklist the given node.\n" + "/who [] List all nodes or show information about the given node.\n" + "/link Link all nodes together.\n" + "/eg Export graph as json file.\n" + "/test Test functionality sending some data to all nodes\n" + "/select Select the active node running the user commands\n" + "/quit Exit this program.\n" + ); + } else { + fprintf(stderr, "Unknown command '/%s'\n", buf); + } +} + +static void parse_input(char *buf) { + static meshlink_node_t *destination; + size_t len; + + if(!buf) + return; + + // Remove newline. + + len = strlen(buf); + + if(len && buf[len - 1] == '\n') + buf[--len] = 0; + + if(len && buf[len - 1] == '\r') + buf[--len] = 0; + + // Ignore empty lines. + + if(!len) + return; + + // Commands start with '/' + + if(*buf == '/') + return parse_command(buf + 1); + + // Lines in the form "name: message..." set the destination node. + + char *msg = buf; + char *colon = strchr(buf, ':'); + + if(colon) { + *colon = 0; + msg = colon + 1; + if(*msg == ' ') + msg++; + + destination = meshlink_get_node(mesh[nodeindex], buf); + if(!destination) { + fprintf(stderr, "Unknown node '%s'\n", buf); + return; + } + } + + if(!destination) { + fprintf(stderr, "Who are you talking to? Write 'name: message...'\n"); + return; + } + + if(!meshlink_send(mesh[nodeindex], destination, msg, strlen(msg) + 1)) { + fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno)); + return; + } + + printf("Message sent to '%s'.\n", destination->name); +} + +int main(int argc, char *argv[]) { + const char *basebase = ".manynodes"; + const char *namesprefix = "machine1"; + const char *graphexporttimeout = NULL; + char buf[1024]; + + if(argc > 1) + n = atoi(argv[1]); + + if(n < 1) { + fprintf(stderr, "Usage: %s [number of local nodes] [confbase] [prefixnodenames] [graphexport timeout]\n", argv[0]); + return 1; + } + + if(argc > 2) + basebase = argv[2]; + + if(argc > 3) + namesprefix = argv[3]; + + if(argc > 4) + graphexporttimeout = argv[4]; + + mesh = calloc(n, sizeof *mesh); + + meshlink_set_log_cb(NULL, MESHLINK_WARNING, log_message); + mkdir(basebase, 0750); + + char filename[PATH_MAX]; + char nodename[100]; + for(int i = 0; i < n; i++) { + snprintf(nodename, sizeof nodename, "%snode%d", namesprefix,i); + snprintf(filename, sizeof filename, "%s/%s", basebase, nodename); + bool itsnew = access(filename, R_OK); + mesh[i] = meshlink_open(filename, nodename, "manynodes", i%_DEV_CLASS_MAX); + meshlink_set_log_cb(mesh[i], MESHLINK_WARNING, log_message); + if(!mesh[i]) { + fprintf(stderr, "errno is: %d\n", meshlink_errno); + fprintf(stderr, "Could not open %s: %s\n", filename, meshlink_strerror(meshlink_errno)); + return 1; + } + } + + int started = 0; + + for(int i = 0; i < n; i++) { + if(!meshlink_start(mesh[i])) + fprintf(stderr, "Could not start node %d: %s\n", i, meshlink_strerror(meshlink_errno)); + else + started++; + } + + if(!started) { + fprintf(stderr, "Could not start any node!\n"); + return 1; + } + + if(graphexporttimeout) + { exportmeshgraph_begin(graphexporttimeout); } + + printf("%d nodes started.\nType /help for a list of commands.\n", started); + + // handle input + while(fgets(buf, sizeof buf, stdin)) + parse_input(buf); + + exportmeshgraph_end(NULL); + + printf("Nodes stopping.\n"); + + for(int i = 0; i < n; i++) + meshlink_close(mesh[i]); + + return 0; +} diff --git a/examples/meshlinkapp.c b/examples/meshlinkapp.c index f506c964..d9e5b7e0 100644 --- a/examples/meshlinkapp.c +++ b/examples/meshlinkapp.c @@ -14,7 +14,7 @@ int main(int argc , char **argv){ meshlink_handle_t* myhandle; - myhandle = meshlink_open(confbase, name); + myhandle = meshlink_open(confbase, name, "meshlinkapp", DEV_CLASS_STATIONARY); //Register callback function for incoming data meshlink_set_receive_cb(myhandle, (meshlink_receive_cb_t)handle_recv_data); diff --git a/src/Makefile.am b/src/Makefile.am index c1d887e9..a2a9305a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -59,7 +59,7 @@ sptps_speed_SOURCES = \ lib_LTLIBRARIES = libmeshlink.la -libmeshlink_la_LDFLAGS = -export-symbols-regex '^meshlink_' +libmeshlink_la_LDFLAGS = -export-symbols-regex '^(meshlink_|devtool_)' libmeshlink_la_SOURCES = \ meshlink.c meshlink.h \ @@ -68,6 +68,7 @@ libmeshlink_la_SOURCES = \ conf.c conf.h \ connection.c connection.h \ crypto.c crypto.h \ + discovery.c discovery.h \ dropin.c dropin.h \ ecdh.h \ ecdsa.h \ @@ -101,13 +102,14 @@ libmeshlink_la_SOURCES = \ system.h \ utils.c utils.h \ xalloc.h \ + devtools.c devtools.h \ $(ed25519_SOURCES) \ $(chacha_poly1305_SOURCES) \ $(utcp_SOURCES) -libmeshlink_la_CFLAGS = -fPIC +libmeshlink_la_CFLAGS = -fPIC -I../avahi/ -libmeshlink_la_LIBADD = -lpthread +libmeshlink_la_LIBADD = -lpthread -luuid ../avahi/avahi-core/.libs/libavahi-core.a ../avahi/avahi-common/.libs/libavahi-common.a libmeshlink_la_SOURCES += \ ed25519/ecdh.c \ diff --git a/src/conf.c b/src/conf.c index aa22c47f..ad4b28b8 100644 --- a/src/conf.c +++ b/src/conf.c @@ -51,7 +51,8 @@ void init_configuration(splay_tree_t **config_tree) { } void exit_configuration(splay_tree_t **config_tree) { - splay_delete_tree(*config_tree); + if(*config_tree) + splay_delete_tree(*config_tree); *config_tree = NULL; } @@ -124,7 +125,7 @@ bool get_config_bool(const config_t *cfg, bool *result) { return true; } - logger(DEBUG_ALWAYS, LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d", + logger(NULL, MESHLINK_ERROR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -137,12 +138,28 @@ bool get_config_int(const config_t *cfg, int *result) { if(sscanf(cfg->value, "%d", result) == 1) return true; - logger(DEBUG_ALWAYS, LOG_ERR, "Integer expected for configuration variable %s in %s line %d", + logger(NULL, MESHLINK_ERROR, "Integer expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; } +bool set_config_int(config_t *cfg, int val) +{ + if(!cfg) + return false; + + char val_str[1024]; + snprintf(val_str, sizeof(val_str), "%d", val); + + if(cfg->value) + free(cfg->value); + + cfg->value = xstrdup(val_str); + + return true; +} + bool get_config_string(const config_t *cfg, char **result) { if(!cfg) return false; @@ -152,6 +169,19 @@ bool get_config_string(const config_t *cfg, char **result) { return true; } +bool set_config_string(config_t *cfg, const char* val) +{ + if(!cfg) + return false; + + if(cfg->value) + free(cfg->value); + + cfg->value = xstrdup(val); + + return true; +} + bool get_config_address(const config_t *cfg, struct addrinfo **result) { struct addrinfo *ai; @@ -165,7 +195,7 @@ bool get_config_address(const config_t *cfg, struct addrinfo **result) { return true; } - logger(DEBUG_ALWAYS, LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d", + logger(NULL, MESHLINK_ERROR, "Hostname or IP address expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -220,7 +250,7 @@ config_t *parse_config_line(char *line, const char *fname, int lineno) { if(!*value) { const char err[] = "No value for variable"; - logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' on line %d while reading config file %s", + logger(NULL, MESHLINK_ERROR, "%s `%s' on line %d while reading config file %s", err, variable, lineno, fname); return NULL; } @@ -250,7 +280,7 @@ bool read_config_file(splay_tree_t *config_tree, const char *fname) { fp = fopen(fname, "r"); if(!fp) { - logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); + logger(NULL, MESHLINK_ERROR, "Cannot open config file %s: %s", fname, strerror(errno)); return false; } @@ -290,6 +320,45 @@ bool read_config_file(splay_tree_t *config_tree, const char *fname) { return result; } +bool write_config_file(const struct splay_tree_t *config_tree, const char *fname) +{ + FILE *fp; + + fp = fopen(fname, "w+"); + + if(!fp) { + logger(NULL, MESHLINK_ERROR, "Cannot open config file %s: %s", fname, strerror(errno)); + return false; + } + + for splay_each(config_t, cnf, config_tree) + { + if(fwrite(cnf->variable, sizeof(char), strlen(cnf->variable), fp) < strlen(cnf->variable)) { + logger(NULL, MESHLINK_ERROR, "Cannot write to config file %s: %s", fname, strerror(errno)); + return false; + } + + if(fwrite(" = ", sizeof(char), 3, fp) < 3) { + logger(NULL, MESHLINK_ERROR, "Cannot write to config file %s: %s", fname, strerror(errno)); + return false; + } + + if(fwrite(cnf->value, sizeof(char), strlen(cnf->value), fp) < strlen(cnf->value)) { + logger(NULL, MESHLINK_ERROR, "Cannot write to config file %s: %s", fname, strerror(errno)); + return false; + } + + if(fwrite("\n", sizeof(char), 1, fp) < 1) { + logger(NULL, MESHLINK_ERROR, "Cannot write to config file %s: %s", fname, strerror(errno)); + return false; + } + } + + fclose(fp); + + return true; +} + bool read_server_config(meshlink_handle_t *mesh) { char filename[PATH_MAX]; bool x; @@ -299,7 +368,7 @@ bool read_server_config(meshlink_handle_t *mesh) { x = read_config_file(mesh->config, filename); if(!x && errno) - logger(DEBUG_ALWAYS, LOG_ERR, "Failed to read `%s': %s", filename, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", filename, strerror(errno)); return x; } @@ -314,6 +383,14 @@ bool read_host_config(meshlink_handle_t *mesh, splay_tree_t *config_tree, const return x; } +bool write_host_config(struct meshlink_handle *mesh, const struct splay_tree_t *config_tree, const char *name) +{ + char filename[PATH_MAX]; + + snprintf(filename,PATH_MAX, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name); + return write_config_file(config_tree, filename); +} + bool append_config_file(meshlink_handle_t *mesh, const char *name, const char *key, const char *value) { char filename[PATH_MAX]; snprintf(filename,PATH_MAX, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name); @@ -321,8 +398,9 @@ bool append_config_file(meshlink_handle_t *mesh, const char *name, const char *k FILE *fp = fopen(filename, "a"); if(!fp) { - logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", filename, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Cannot open config file %s: %s", filename, strerror(errno)); } else { + fprintf(fp, "%s = %s\n", key, value); fclose(fp); } diff --git a/src/conf.h b/src/conf.h index 7c0335d7..c3cf6f7d 100644 --- a/src/conf.h +++ b/src/conf.h @@ -40,14 +40,18 @@ extern config_t *lookup_config(struct splay_tree_t *, char *); extern config_t *lookup_config_next(struct splay_tree_t *, const config_t *); extern bool get_config_bool(const config_t *, bool *); extern bool get_config_int(const config_t *, int *); +extern bool set_config_int(config_t *, int); extern bool get_config_string(const config_t *, char **); +extern bool set_config_string(config_t *, const char *); extern bool get_config_address(const config_t *, struct addrinfo **); extern config_t *parse_config_line(char *, const char *, int); extern bool read_config_file(struct splay_tree_t *, const char *); +extern bool write_config_file(const struct splay_tree_t *, const char *); extern bool read_server_config(struct meshlink_handle *mesh); extern bool read_host_config(struct meshlink_handle *mesh, struct splay_tree_t *, const char *); +extern bool write_host_config(struct meshlink_handle *mesh, const struct splay_tree_t *, const char *); extern bool append_config_file(struct meshlink_handle *mesh, const char *, const char *, const char *); #endif /* __MESHLINK_CONF_H__ */ diff --git a/src/connection.c b/src/connection.c index 05a9d653..cbe7a9d6 100644 --- a/src/connection.c +++ b/src/connection.c @@ -36,8 +36,13 @@ void init_connections(meshlink_handle_t *mesh) { } void exit_connections(meshlink_handle_t *mesh) { - list_delete_list(mesh->connections); + if(mesh->connections) + list_delete_list(mesh->connections); + free_connection(mesh->everyone); + + mesh->connections = NULL; + mesh->everyone = NULL; } connection_t *new_connection(void) { diff --git a/src/connection.h b/src/connection.h index 5c112175..c3c219be 100644 --- a/src/connection.h +++ b/src/connection.h @@ -64,8 +64,6 @@ typedef struct connection_t { int socket; /* socket used for this connection */ uint32_t options; /* options for this connection */ connection_status_t status; /* status info */ - int estimated_weight; /* estimation for the weight of the edge for this connection */ - struct timeval start; /* time this connection was started, used for above estimation */ struct outgoing_t *outgoing; /* used to keep track of outgoing connections */ struct meshlink_handle *mesh; /* the mesh this connection belongs to */ diff --git a/src/devtools.c b/src/devtools.c new file mode 100644 index 00000000..d287f3c9 --- /dev/null +++ b/src/devtools.c @@ -0,0 +1,160 @@ + +#include "system.h" + +#include "logger.h" +#include "meshlink_internal.h" +#include "node.h" +#include "splay_tree.h" +#include "netutl.h" +#include "xalloc.h" + +#include "devtools.h" + +static int node_compare(const void *a, const void *b) +{ + if(a < b) + return -1; + + if(a > b) + return 1; + + return 0; +} + +static bool fstrwrite(const char* str, FILE* stream) +{ + size_t len = strlen(str); + + if(fwrite((void*)str, 1, len, stream) != len) + return false; + + return true; +} + +static const char* itoa(int value) +{ + static char buffer[sizeof(int) * 8 + 1]; // not thread safe + + if(snprintf(buffer, sizeof(buffer), "%d", value) == -1) + return ""; + + return buffer; +} + +bool devtool_export_json_all_edges_state(meshlink_handle_t *mesh, FILE* stream) +{ + bool result = true; + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + // export edges and nodes + size_t node_count = 0; + size_t edge_count = 0; + + meshlink_node_t **nodes = meshlink_get_all_nodes(mesh, NULL, &node_count); + meshlink_edge_t **edges = meshlink_get_all_edges_state(mesh, NULL, &edge_count); + + if((!nodes && node_count != 0) || (!edges && edge_count != 0)) + { goto fail; } + + // export begin + if(!fstrwrite("{\n", stream)) + goto fail; + + // export nodes + if(!fstrwrite("\t\"nodes\": {\n", stream)) + goto fail; + + for(size_t i = 0; i < node_count; ++i) + { + if(!fstrwrite("\t\t\"", stream) || !fstrwrite(((node_t*)nodes[i])->name, stream) || !fstrwrite("\": {\n", stream)) + goto fail; + + if(!fstrwrite("\t\t\t\"name\": \"", stream) || !fstrwrite(((node_t*)nodes[i])->name, stream) || !fstrwrite("\",\n", stream)) + goto fail; + + if(!fstrwrite("\t\t\t\"options\": ", stream) || !fstrwrite(itoa(((node_t*)nodes[i])->options), stream) || !fstrwrite(",\n", stream)) + goto fail; + + if(!fstrwrite("\t\t\t\"devclass\": ", stream) || !fstrwrite(itoa(((node_t*)nodes[i])->devclass), stream) || !fstrwrite("\n", stream)) + goto fail; + + if(!fstrwrite((i+1) != node_count ? "\t\t},\n" : "\t\t}\n", stream)) + goto fail; + } + + if(!fstrwrite("\t},\n", stream)) + goto fail; + + // export edges + + if(!fstrwrite("\t\"edges\": {\n", stream)) + goto fail; + + for(size_t i = 0; i < edge_count; ++i) + { + if(!fstrwrite("\t\t\"", stream) || !fstrwrite(edges[i]->from->name, stream) || !fstrwrite("_to_", stream) || !fstrwrite(edges[i]->to->name, stream) || !fstrwrite("\": {\n", stream)) + goto fail; + + if(!fstrwrite("\t\t\t\"from\": \"", stream) || !fstrwrite(edges[i]->from->name, stream) || !fstrwrite("\",\n", stream)) + goto fail; + + if(!fstrwrite("\t\t\t\"to\": \"", stream) || !fstrwrite(edges[i]->to->name, stream) || !fstrwrite("\",\n", stream)) + goto fail; + + char *host = NULL, *port = NULL, *address = NULL; + sockaddr2str((const sockaddr_t *)&(edges[i]->address), &host, &port); + + if(host && port) { + xasprintf(&address, "{ \"host\": \"%s\", \"port\": %s }", host, port); + } + + free(host); + free(port); + + if(!fstrwrite("\t\t\t\"address\": ", stream) || !fstrwrite(address ? address : "null", stream) || !fstrwrite(",\n", stream)) + { + free(address); + goto fail; + } + + free(address); + + if(!fstrwrite("\t\t\t\"options\": ", stream) || !fstrwrite(itoa(edges[i]->options), stream) || !fstrwrite(",\n", stream)) + goto fail; + + if(!fstrwrite("\t\t\t\"weight\": ", stream) || !fstrwrite(itoa(edges[i]->weight), stream) || !fstrwrite("\n", stream)) + goto fail; + + if(!fstrwrite((i+1) != edge_count ? "\t\t},\n" : "\t\t}\n", stream)) + goto fail; + } + + if(!fstrwrite("\t}\n", stream)) + goto fail; + + // DONE! + + if(!fstrwrite("}", stream)) + goto fail; + + goto done; + +fail: + result = false; + +done: + + if(nodes) + { free(nodes); } + + for(size_t i = 0; edges && i < edge_count; ++i) + { free(edges[i]); } + + if(nodes) + { free(edges); } + + pthread_mutex_unlock(&(mesh->mesh_mutex)); + + return result; +} diff --git a/src/devtools.h b/src/devtools.h new file mode 100644 index 00000000..9ac58d52 --- /dev/null +++ b/src/devtools.h @@ -0,0 +1,6 @@ +#ifndef __MESHLINK_DEVTOOLS_H__ +#define __MESHLINK_DEVTOOLS_H__ + +extern bool devtool_export_json_all_edges_state(meshlink_handle_t *mesh, FILE* stream); + +#endif diff --git a/src/discovery.c b/src/discovery.c new file mode 100644 index 00000000..fa4e13c3 --- /dev/null +++ b/src/discovery.c @@ -0,0 +1,613 @@ + +#include "meshlink_internal.h" +#include "discovery.h" +#include "sockaddr.h" +#include "logger.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp" +#define MESHLINK_MDNS_NAME_KEY "name" +#define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint" + +static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata) +{ + meshlink_handle_t *mesh = userdata; + + // asserts + assert(mesh != NULL); + assert(mesh->avahi_server != NULL); + assert(mesh->avahi_poll != NULL); + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + /* Called whenever the entry group state changes */ + switch(state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + logger(mesh, MESHLINK_DEBUG, "Avahi Service successfully established.\n"); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + logger(mesh, MESHLINK_WARNING, "Avahi Service collision.\n"); + // @TODO can we just set a new name and retry? + break; + + case AVAHI_ENTRY_GROUP_FAILURE : + /* Some kind of failure happened while we were registering our services */ + logger(mesh, MESHLINK_ERROR, "Avahi Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server))); + avahi_simple_poll_quit(mesh->avahi_poll); + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + ; + } + + pthread_mutex_unlock(&(mesh->mesh_mutex)); +} + + +static void discovery_create_services(meshlink_handle_t *mesh) +{ + char *txt_name = NULL; + + // asserts + assert(mesh != NULL); + assert(mesh->name != NULL); + assert(mesh->myport != NULL); + assert(mesh->avahi_server != NULL); + assert(mesh->avahi_poll != NULL); + assert(mesh->avahi_servicetype != NULL); + assert(mesh->self != NULL); + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + logger(mesh, MESHLINK_DEBUG, "Adding service\n"); + + /* Ifthis is the first time we're called, let's create a new entry group */ + if(!mesh->avahi_group) + { + if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh))) + { + logger(mesh, MESHLINK_ERROR, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server))); + goto fail; + } + } + + /* Create txt records */ + size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1; + txt_name = malloc(txt_name_len); + + if(txt_name == NULL) + { + logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n"); + goto fail; + } + + snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name); + + char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1]; + snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self)); + + /* Add the service */ + int ret = 0; + if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self), mesh->avahi_servicetype, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0) + { + logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", avahi_strerror(ret)); + goto fail; + } + + /* Tell the server to register the service */ + if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0) + { + logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", avahi_strerror(ret)); + goto fail; + } + + goto done; + +fail: + avahi_simple_poll_quit(mesh->avahi_poll); + +done: + if(txt_name) + { free(txt_name); } + + pthread_mutex_unlock(&(mesh->mesh_mutex)); +} + +static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata) +{ + meshlink_handle_t *mesh = userdata; + + // asserts + assert(mesh != NULL); + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + switch(state) + { + case AVAHI_SERVER_RUNNING: + { + /* The serve has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if(!mesh->avahi_group) + { + discovery_create_services(mesh); + } + } + break; + + case AVAHI_SERVER_COLLISION: + { + // asserts + assert(mesh->avahi_server != NULL); + assert(mesh->avahi_poll != NULL); + + /* A host name collision happened. Let's pick a new name for the server */ + uuid_t hostname; + uuid_generate(hostname); + + char hostnamestr[36+1]; + uuid_unparse_lower(hostname, hostnamestr); + + logger(mesh, MESHLINK_WARNING, "Avahi host name collision, retrying with '%s'\n", hostnamestr); + int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr); + + if(result < 0) + { + logger(mesh, MESHLINK_ERROR, "Avahi failed to set new host name: %s\n", avahi_strerror(result)); + avahi_simple_poll_quit(mesh->avahi_poll); + } + } + break; + + case AVAHI_SERVER_REGISTERING: + { + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if(mesh->avahi_group) + { + avahi_s_entry_group_reset(mesh->avahi_group); + mesh->avahi_group = NULL; + } + } + break; + + case AVAHI_SERVER_FAILURE: + { + // asserts + assert(mesh->avahi_server != NULL); + assert(mesh->avahi_poll != NULL); + + /* Terminate on failure */ + logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server))); + avahi_simple_poll_quit(mesh->avahi_poll); + } + break; + + case AVAHI_SERVER_INVALID: + break; + } + + pthread_mutex_unlock(&(mesh->mesh_mutex)); +} + +static void discovery_resolve_callback(AvahiSServiceResolver *resolver, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata) +{ + meshlink_handle_t *mesh = userdata; + + // asserts + assert(resolver != NULL); + assert(mesh != NULL); + assert(mesh->avahi_server != NULL); + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + /* Called whenever a service has been resolved successfully or timed out */ + switch(event) + { + case AVAHI_RESOLVER_FAILURE: + { + // asserts + assert(name != NULL); + assert(type != NULL); + assert(domain != NULL); + + logger(mesh, MESHLINK_WARNING, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_server_errno(mesh->avahi_server))); + } + break; + + case AVAHI_RESOLVER_FOUND: + { + // asserts + assert(name != NULL); + assert(type != NULL); + assert(domain != NULL); + assert(host_name != NULL); + assert(address != NULL); + assert(txt != NULL); + + char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt; + + logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain); + + avahi_address_snprint(straddr, sizeof(straddr), address); + strtxt = avahi_string_list_to_string(txt); + logger(mesh, MESHLINK_DEBUG, + "\t%s:%u (%s)\n" + "\tTXT=%s\n" + "\tcookie is %u\n" + "\tis_local: %i\n" + "\twide_area: %i\n" + "\tmulticast: %i\n" + "\tcached: %i\n", + host_name, port, straddr, + strtxt, + avahi_string_list_get_service_cookie(txt), + !!(flags & AVAHI_LOOKUP_RESULT_LOCAL), + !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA), + !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST), + !!(flags & AVAHI_LOOKUP_RESULT_CACHED)); + avahi_free(strtxt); + + // retrieve fingerprint + AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY); + AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY); + + if(node_name_li != NULL && node_fp_li != NULL) + { + char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY); + char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY); + + if(node_name[0] == '=' && node_fp[0] == '=') + { + node_name += 1; + node_fp += 1; + + meshlink_node_t *node = meshlink_get_node(mesh, node_name); + + if(node != NULL) + { + logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name); + + sockaddr_t naddress; + memset(&naddress, 0, sizeof(naddress)); + + switch(address->proto) + { + case AVAHI_PROTO_INET: + { + naddress.in.sin_family = AF_INET; + naddress.in.sin_port = htons(port); + naddress.in.sin_addr.s_addr = address->data.ipv4.address; + } + break; + + case AVAHI_PROTO_INET6: + { + naddress.in6.sin6_family = AF_INET6; + naddress.in6.sin6_port = htons(port); + memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr)); + } + break; + + default: + naddress.unknown.family = AF_UNKNOWN; + break; + } + + if(naddress.unknown.family != AF_UNKNOWN) + { + meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress); + } + else + { + logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name); + } + } + else + { + logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name); + } + } + else + { + logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n"); + } + } + else + { + logger(mesh, MESHLINK_WARNING, "TXT records missing.\n"); + } + } + break; + } + + avahi_s_service_resolver_free(resolver); + + pthread_mutex_unlock(&(mesh->mesh_mutex)); +} + +static void discovery_browse_callback(AvahiSServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) +{ + meshlink_handle_t *mesh = userdata; + + // asserts + assert(mesh != NULL); + assert(mesh->avahi_server != NULL); + assert(mesh->avahi_poll != NULL); + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + switch (event) + { + case AVAHI_BROWSER_FAILURE: + { + logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server))); + avahi_simple_poll_quit(mesh->avahi_poll); + } + break; + + case AVAHI_BROWSER_NEW: + { + // asserts + assert(name != NULL); + assert(type != NULL); + assert(domain != NULL); + + logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain); + /* We ignore the returned resolver object. In the callback + function we free it. Ifthe server is terminated before + the callback function is called the server will free + the resolver for us. */ + if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh))) + { + logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server))); + } + } + break; + + case AVAHI_BROWSER_REMOVE: + { + // asserts + assert(name != NULL); + assert(type != NULL); + assert(domain != NULL); + + logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain); + } + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + { + logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + } + break; + } + + pthread_mutex_unlock(&(mesh->mesh_mutex)); +} + +static void *discovery_loop(void *userdata) +{ + meshlink_handle_t *mesh = userdata; + + // asserts + assert(mesh != NULL); + assert(mesh->avahi_poll != NULL); + + avahi_simple_poll_loop(mesh->avahi_poll); + + return NULL; +} + +static void discovery_log_cb(AvahiLogLevel level, const char *txt) +{ + meshlink_log_level_t mlevel = MESHLINK_CRITICAL; + + switch(level) + { + case AVAHI_LOG_ERROR: + mlevel = MESHLINK_ERROR; + break; + + case AVAHI_LOG_WARN: + mlevel = MESHLINK_WARNING; + break; + + case AVAHI_LOG_NOTICE: + case AVAHI_LOG_INFO: + mlevel = MESHLINK_INFO; + break; + + case AVAHI_LOG_DEBUG: + mlevel = MESHLINK_DEBUG; + break; + } + + logger(NULL, mlevel, "%s\n", txt); +} + +bool discovery_start(meshlink_handle_t *mesh) +{ + logger(mesh, MESHLINK_DEBUG, "discovery_start called\n"); + + // asserts + assert(mesh != NULL); + assert(mesh->avahi_poll == NULL); + assert(mesh->avahi_server == NULL); + assert(mesh->avahi_browser == NULL); + assert(mesh->discovery_threadstarted == false); + assert(mesh->avahi_servicetype == NULL); + + // handle avahi logs + avahi_set_log_function(discovery_log_cb); + + // create service type string + size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1; + mesh->avahi_servicetype = malloc(servicetype_strlen); + + if(mesh->avahi_servicetype == NULL) + { + logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n"); + goto fail; + } + + snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname); + + // Allocate discovery loop object + if(!(mesh->avahi_poll = avahi_simple_poll_new())) + { + logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n"); + goto fail; + } + + // generate some unique host name (we actually do not care about it) + uuid_t hostname; + uuid_generate(hostname); + + char hostnamestr[36+1]; + uuid_unparse_lower(hostname, hostnamestr); + + // Let's set the host name for this server. + AvahiServerConfig config; + avahi_server_config_init(&config); + config.host_name = avahi_strdup(hostnamestr); + config.publish_workstation = 0; + config.disallow_other_stacks = 0; + config.publish_hinfo = 0; + config.publish_addresses = 1; + config.publish_no_reverse = 1; + + /* Allocate a new server */ + int error; + mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error); + + /* Free the configuration data */ + avahi_server_config_free(&config); + + /* Check wether creating the server object succeeded */ + if(!mesh->avahi_server) + { + logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error)); + goto fail; + } + + // Create the service browser + if(!(mesh->avahi_browser = avahi_s_service_browser_new(mesh->avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mesh->avahi_servicetype, NULL, 0, discovery_browse_callback, mesh))) + { + logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server))); + goto fail; + } + + // Start the discovery thread + if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) + { + logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno)); + memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread); + goto fail; + } + + mesh->discovery_threadstarted = true; + + return true; + +fail: + if(mesh->avahi_browser != NULL) + { + avahi_s_service_browser_free(mesh->avahi_browser); + mesh->avahi_browser = NULL; + } + + if(mesh->avahi_server != NULL) + { + avahi_server_free(mesh->avahi_server); + mesh->avahi_server = NULL; + } + + if(mesh->avahi_poll != NULL) + { + avahi_simple_poll_free(mesh->avahi_poll); + mesh->avahi_poll = NULL; + } + + if(mesh->avahi_servicetype != NULL) + { + free(mesh->avahi_servicetype); + mesh->avahi_servicetype = NULL; + } + + return false; +} + +void discovery_stop(meshlink_handle_t *mesh) +{ + logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n"); + + // asserts + assert(mesh != NULL); + + // Shut down + if(mesh->avahi_poll) + { + avahi_simple_poll_quit(mesh->avahi_poll); + } + + // Wait for the discovery thread to finish + if(mesh->discovery_threadstarted == true) + { + pthread_join(mesh->discovery_thread, NULL); + mesh->discovery_threadstarted = false; + } + + // Clean up resources + if(mesh->avahi_browser != NULL) + { + avahi_s_service_browser_free(mesh->avahi_browser); + mesh->avahi_browser = NULL; + } + + if(mesh->avahi_group) + { + avahi_s_entry_group_reset(mesh->avahi_group); + avahi_s_entry_group_free(mesh->avahi_group); + mesh->avahi_group = NULL; + } + + if(mesh->avahi_server != NULL) + { + avahi_server_free(mesh->avahi_server); + mesh->avahi_server = NULL; + } + + if(mesh->avahi_poll != NULL) + { + avahi_simple_poll_free(mesh->avahi_poll); + mesh->avahi_poll = NULL; + } + + if(mesh->avahi_servicetype != NULL) + { + free(mesh->avahi_servicetype); + mesh->avahi_servicetype = NULL; + } +} diff --git a/src/discovery.h b/src/discovery.h new file mode 100644 index 00000000..0455d6ed --- /dev/null +++ b/src/discovery.h @@ -0,0 +1,10 @@ + +#ifndef __MESHLINK_DISCOVERY_H__ +#define __MESHLINK_DISCOVERY_H__ + +#include + +extern bool discovery_start(meshlink_handle_t *mesh); +extern void discovery_stop(meshlink_handle_t *mesh); + +#endif diff --git a/src/ed25519/.dirstamp b/src/ed25519/.dirstamp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ed25519/ecdsa.c b/src/ed25519/ecdsa.c index cabec3a2..a63dedab 100644 --- a/src/ed25519/ecdsa.c +++ b/src/ed25519/ecdsa.c @@ -38,14 +38,14 @@ ecdsa_t *ecdsa_set_base64_public_key(const char *p) { int len = strlen(p); if(len != 43) { - logger(DEBUG_ALWAYS, LOG_ERR, "Invalid size %d for public key!", len); + logger(NULL, MESHLINK_ERROR, "Invalid size %d for public key!", len); return 0; } ecdsa_t *ecdsa = xzalloc(sizeof *ecdsa); len = b64decode(p, ecdsa->public, len); if(len != 32) { - logger(DEBUG_ALWAYS, LOG_ERR, "Invalid format of public key! len = %d", len); + logger(NULL, MESHLINK_ERROR, "Invalid format of public key! len = %d", len); free(ecdsa); return 0; } diff --git a/src/ed25519/seed.c b/src/ed25519/seed.c index 11a2e3ec..dda3ccb2 100644 --- a/src/ed25519/seed.c +++ b/src/ed25519/seed.c @@ -30,7 +30,9 @@ int ed25519_create_seed(unsigned char *seed) { return 1; } - fread(seed, 1, 32, f); + if(fread(seed, 32, 1, f) != 1) + return 1; + fclose(f); #endif diff --git a/src/edge.c b/src/edge.c index 455b2301..efb90dfe 100644 --- a/src/edge.c +++ b/src/edge.c @@ -61,7 +61,9 @@ void free_edge_tree(splay_tree_t *edge_tree) { } void exit_edges(meshlink_handle_t *mesh) { - splay_delete_tree(mesh->edges); + if(mesh->edges) + splay_delete_tree(mesh->edges); + mesh->edges = NULL; } /* Creation and deletion of connection elements */ diff --git a/src/event.c b/src/event.c index d1a0e256..62daea6f 100644 --- a/src/event.c +++ b/src/event.c @@ -81,6 +81,8 @@ void io_del(event_loop_t *loop, io_t *io) { if(!io->cb) return; + loop->deletion = true; + io_set(loop, io, 0); splay_unlink_node(&loop->ios, &io->node); @@ -115,6 +117,8 @@ void timeout_del(event_loop_t *loop, timeout_t *timeout) { if(!timeout->cb) return; + loop->deletion = true; + splay_unlink_node(&loop->timeouts, &timeout->node); timeout->cb = 0; timeout->tv = (struct timeval){0, 0}; @@ -167,11 +171,13 @@ void signal_del(event_loop_t *loop, signal_t *sig) { if(!sig->cb) return; + loop->deletion = true; + splay_unlink_node(&loop->signals, &sig->node); sig->cb = NULL; } -bool event_loop_run(event_loop_t *loop) { +bool event_loop_run(event_loop_t *loop, pthread_mutex_t *mutex) { loop->running = true; fd_set readable; @@ -205,7 +211,12 @@ bool event_loop_run(event_loop_t *loop) { fds = last->fd + 1; } + // release mesh mutex during select + if(mutex) + pthread_mutex_unlock(mutex); int n = select(fds, &readable, &writable, NULL, tv); + if(mutex) + pthread_mutex_lock(mutex); if(n < 0) { if(sockwouldblock(errno)) @@ -217,11 +228,21 @@ bool event_loop_run(event_loop_t *loop) { if(!n) continue; + // Normally, splay_each allows the current node to be deleted. However, + // it can be that one io callback triggers the deletion of another io, + // so we have to detect this and break the loop. + + loop->deletion = false; + for splay_each(io_t, io, &loop->ios) { - if(FD_ISSET(io->fd, &writable)) + if(FD_ISSET(io->fd, &writable) && io->cb) io->cb(loop, io->data, IO_WRITE); - else if(FD_ISSET(io->fd, &readable)) + if(loop->deletion) + break; + if(FD_ISSET(io->fd, &readable) && io->cb) io->cb(loop, io->data, IO_READ); + if(loop->deletion) + break; } } diff --git a/src/event.h b/src/event.h index c90e5067..4eb18251 100644 --- a/src/event.h +++ b/src/event.h @@ -60,6 +60,7 @@ struct event_loop_t { volatile bool running; struct timeval now; + bool deletion; splay_tree_t ios; splay_tree_t timeouts; @@ -85,7 +86,7 @@ extern void signal_del(event_loop_t *loop, signal_t *sig); extern void event_loop_init(event_loop_t *loop); extern void event_loop_exit(event_loop_t *loop); -extern bool event_loop_run(event_loop_t *loop); +extern bool event_loop_run(event_loop_t *loop, pthread_mutex_t *mutex); extern void event_loop_flush_output(event_loop_t *loop); extern void event_loop_stop(event_loop_t *loop); diff --git a/src/graph.c b/src/graph.c index 34af237e..2453d6f1 100644 --- a/src/graph.c +++ b/src/graph.c @@ -67,7 +67,7 @@ static void mst_kruskal(meshlink_handle_t *mesh) { for list_each(connection_t, c, mesh->connections) c->status.mst = false; - logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Running Kruskal's algorithm:"); + logger(mesh, MESHLINK_DEBUG, "Running Kruskal's algorithm:"); /* Clear visited status on nodes */ @@ -102,7 +102,7 @@ static void mst_kruskal(meshlink_handle_t *mesh) { if(e->reverse->connection) e->reverse->connection->status.mst = true; - logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name, e->to->name, e->weight); + logger(mesh, MESHLINK_DEBUG, " Adding edge %s - %s weight %d", e->from->name, e->to->name, e->weight); if(skipped) { skipped = false; @@ -139,7 +139,7 @@ static void sssp_bfs(meshlink_handle_t *mesh) { /* Loop while todo_list is filled */ for list_each(node_t, n, todo_list) { /* "n" is the node from which we start */ - logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Examining edges from %s", n->name); + logger(mesh, MESHLINK_DEBUG, " Examining edges from %s", n->name); if(n->distance < 0) abort(); @@ -202,10 +202,10 @@ static void check_reachability(meshlink_handle_t *mesh) { n->last_state_change = mesh->loop.now.tv_sec; if(n->status.reachable) { - logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became reachable", + logger(mesh, MESHLINK_DEBUG, "Node %s (%s) became reachable", n->name, n->hostname); } else { - logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became unreachable", + logger(mesh, MESHLINK_DEBUG, "Node %s (%s) became unreachable", n->name, n->hostname); } diff --git a/src/logger.c b/src/logger.c index ea29e3f6..ecb55a53 100644 --- a/src/logger.c +++ b/src/logger.c @@ -24,9 +24,14 @@ #include "sptps.h" // TODO: refactor logging code to use a meshlink_handle_t *. -void logger(int level, int priority, const char *format, ...) { - //if(level > mesh->debug_level) - // return; +void logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *format, ...) { + if(mesh) { + if(level < mesh->log_level || !mesh->log_cb) + return; + } else { + if(level < global_log_level || !global_log_cb) + return; + } va_list ap; char message[1024] = ""; @@ -38,5 +43,8 @@ void logger(int level, int priority, const char *format, ...) { if(len > 0 && len < sizeof message && message[len - 1] == '\n') message[len - 1] = 0; - fprintf(stderr, "%s\n", message); + if(mesh) + mesh->log_cb(mesh, level, message); + else + global_log_cb(NULL, level, message); } diff --git a/src/logger.h b/src/logger.h index 4dca2a95..94482697 100644 --- a/src/logger.h +++ b/src/logger.h @@ -20,49 +20,8 @@ #ifndef __MESHLINK_LOGGER_H__ #define __MESHLINK_LOGGER_H__ -typedef enum debug_t { - DEBUG_NOTHING = 0, /* Quiet mode, only show starting/stopping of the daemon */ - DEBUG_ALWAYS = 0, - DEBUG_CONNECTIONS = 1, /* Show (dis)connects of other nodes via TCP */ - DEBUG_ERROR = 2, /* Show error messages received from other hosts */ - DEBUG_STATUS = 2, /* Show status messages received from other hosts */ - DEBUG_PROTOCOL = 3, /* Show the requests that are sent/received */ - DEBUG_META = 4, /* Show contents of every request that is sent/received */ - DEBUG_TRAFFIC = 5, /* Show network traffic information */ - DEBUG_PACKET = 6, /* Show contents of each packet that is being sent/received */ - DEBUG_SCARY_THINGS = 10 /* You have been warned */ -} debug_t; +#include "meshlink_internal.h" -typedef enum logmode_t { - LOGMODE_NULL, - LOGMODE_STDERR, - LOGMODE_SYSLOG -} logmode_t; - -#ifdef HAVE_MINGW -#define LOG_EMERG EVENTLOG_ERROR_TYPE -#define LOG_ALERT EVENTLOG_ERROR_TYPE -#define LOG_CRIT EVENTLOG_ERROR_TYPE -#define LOG_ERR EVENTLOG_ERROR_TYPE -#define LOG_WARNING EVENTLOG_WARNING_TYPE -#define LOG_NOTICE EVENTLOG_INFORMATION_TYPE -#define LOG_INFO EVENTLOG_INFORMATION_TYPE -#define LOG_DEBUG EVENTLOG_INFORMATION_TYPE -#else -#ifndef HAVE_SYSLOG_H -enum { - LOG_EMERG, - LOG_ALERT, - LOG_CRIT, - LOG_ERR, - LOG_WARNING, - LOG_NOTICE, - LOG_INFO, - LOG_DEBUG, -}; -#endif -#endif - -extern void logger(int, int, const char *, ...) __attribute__ ((__format__(printf, 3, 4))); +extern void logger(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *format, ...) __attribute__ ((__format__(printf, 3, 4))); #endif /* __MESHLINK_LOGGER_H__ */ diff --git a/src/meshlink++.h b/src/meshlink++.h index a1911aff..ef578b11 100644 --- a/src/meshlink++.h +++ b/src/meshlink++.h @@ -21,6 +21,7 @@ #define MESHLINKPP_H #include +#include // for 'placement new' namespace meshlink { class mesh; @@ -86,9 +87,30 @@ namespace meshlink { /// A class describing a MeshLink mesh. class mesh: public meshlink_handle_t { - public: - // TODO: delete constructor, add a destructor. - + public: + mesh() : meshlink_handle_t() {} + + virtual ~mesh() { + meshlink_close(this); + } + + /** instead of registerin callbacks you derive your own class and overwrite the following abstract member functions. + * These functions are run in MeshLink's own thread. + * It is therefore important that these functions use apprioriate methods (queues, pipes, locking, etc.) + * to hand the data over to the application's thread. + * These functions should also not block itself and return as quickly as possible. + * The default member functions are no-ops, so you are not required to overwrite all these member functions + */ + + /// This function is called whenever another node sends data to the local node. + virtual void receive(node* source, const void* data, size_t length) { /* do nothing */ } + + /// This functions is called whenever another node's status changed. + virtual void node_status(node* peer, bool reachable) { /* do nothing */ } + + /// This functions is called whenever MeshLink has some information to log. + virtual void log(log_level_t level, const char* message) { /* do nothing */ } + /// Start MeshLink. /** This function causes MeshLink to open network sockets, make outgoing connections, and * create a new thread, which will handle all network I/O. @@ -96,6 +118,9 @@ namespace meshlink { * @return This function will return true if MeshLink has succesfully started its thread, false otherwise. */ bool start() { + meshlink_set_receive_cb (this, &receive_trampoline); + meshlink_set_node_status_cb(this, &node_status_trampoline); + meshlink_set_log_cb (this, MESHLINK_DEBUG, &log_trampoline); return meshlink_start(this); } @@ -107,46 +132,6 @@ namespace meshlink { meshlink_stop(this); } - /// Set the receive callback. - /** This functions sets the callback that is called whenever another node sends data to the local node. - * The callback is run in MeshLink's own thread. - * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) - * to hand the data over to the application's thread. - * The callback should also not block itself and return as quickly as possible. - * - * @param cb A pointer to the function which will be called when another node sends data to the local node. - */ - void set_receive_cb(receive_cb_t cb) { - meshlink_set_receive_cb(this, (meshlink_receive_cb_t)cb); - } - - /// Set the node status callback. - /** This functions sets the callback that is called whenever another node's status changed. - * The callback is run in MeshLink's own thread. - * It is therefore important that the callback uses apprioriate methods (queues, pipes, locking, etc.) - * to hand the data over to the application's thread. - * The callback should also not block itself and return as quickly as possible. - * - * @param cb A pointer to the function which will be called when another node's status changes. - */ - void set_node_status_cb(node_status_cb_t cb) { - meshlink_set_node_status_cb(this, (meshlink_node_status_cb_t)cb); - } - - /// Set the log callback. - /** This functions sets the callback that is called whenever MeshLink has some information to log. - * The callback is run in MeshLink's own thread. - * It is important that the callback uses apprioriate methods (queues, pipes, locking, etc.) - * to hand the data over to the application's thread. - * The callback should also not block itself and return as quickly as possible. - * - * @param level An enum describing the minimum severity level. Debugging information with a lower level will not trigger the callback. - * @param cb A pointer to the function which will be called when another node sends data to the local node. - */ - void set_log_cb(meshlink_log_level_t level, log_cb_t cb) { - meshlink_set_log_cb(this, level, (meshlink_log_cb_t)cb); - } - /// Send data to another node. /** This functions sends one packet of data to another node in the mesh. * The packet is sent using UDP semantics, which means that @@ -182,10 +167,11 @@ namespace meshlink { * @param nodes A pointer to an array of pointers to meshlink::node, which should be allocated by the application. * @param nmemb The maximum number of pointers that can be stored in the nodes array. * - * @return The number of known nodes. This can be larger than nmemb, in which case not all nodes were stored in the nodes array. + * @return The number of known nodes, or -1 in case of an error. + * This can be larger than nmemb, in which case not all nodes were stored in the nodes array. */ - size_t get_all_nodes(node **nodes, size_t nmemb) { - return meshlink_get_all_nodes(this, (meshlink_node_t **)nodes, nmemb); + node **get_all_nodes(node **nodes, size_t *nmemb) { + return (node **)meshlink_get_all_nodes(this, (meshlink_node_t **)nodes, nmemb); } /// Sign data using the local node's MeshLink key. @@ -319,7 +305,7 @@ namespace meshlink { * @return A handle for the channel, or NULL in case of an error. */ channel *channel_open(node *node, uint16_t port, channel_receive_cb_t cb, const void *data, size_t len) { - return (channel *)meshlink_channel_open(this, node, port, (meshlink_channel_receive_cb_t)recv, data, len); + return (channel *)meshlink_channel_open(this, node, port, (meshlink_channel_receive_cb_t)cb, data, len); } /// Partially close a reliable stream channel. @@ -358,6 +344,29 @@ namespace meshlink { return meshlink_channel_send(this, channel, data, len); } + private: + // non-copyable: + mesh(const mesh&) /* TODO: C++11: = delete */; + void operator=(const mesh&) /* TODO: C++11: = delete */ ; + + /// static callback trampolines: + static void receive_trampoline(meshlink_handle_t* handle, meshlink_node_t* source, const void* data, size_t length) + { + mesh* that = static_cast(handle); + that->receive(static_cast(source), data, length); + } + + static void node_status_trampoline(meshlink_handle_t* handle, meshlink_node_t* peer, bool reachable) + { + mesh* that = static_cast(handle); + that->node_status(static_cast(peer), reachable); + } + + static void log_trampoline(meshlink_handle_t* handle, log_level_t level, const char* message) + { + mesh* that = static_cast(handle); + that->log(level, message); + } }; /// Initialize MeshLink's configuration directory. @@ -372,11 +381,15 @@ namespace meshlink { * * @param confbase The directory in which MeshLink will store its configuration files. * @param name The name which this instance of the application will use in the mesh. + * @param appname The application name which will be used in the mesh. + * @param dclass The device class which will be used in the mesh. * * @return This function will return a pointer to a meshlink::mesh if MeshLink has succesfully set up its configuration files, NULL otherwise. */ - static mesh *open(const char *confbase, const char *name) { - return (mesh *)meshlink_open(confbase, name); + template + static MESH* open(const char *confbase, const char *name, const char* appname, dev_class_t devclass) { + void* mp = (void *)meshlink_open_with_size(confbase, name, appname, devclass, sizeof(MESH)); + return new (mp) MESH; } /// Close the MeshLink handle. @@ -387,6 +400,11 @@ namespace meshlink { static void close(mesh *mesh) { meshlink_close(mesh); } -}; + + static const char *strerror(errno_t err = meshlink_errno) { + return meshlink_strerror(err); + } + +} #endif // MESHLINKPP_H diff --git a/src/meshlink.c b/src/meshlink.c index f44dba47..ef02580e 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -21,6 +21,8 @@ #define VAR_MULTIPLE 4 /* Multiple statements allowed */ #define VAR_OBSOLETE 8 /* Should not be used anymore */ #define VAR_SAFE 16 /* Variable is safe when accepting invitations */ +#define MAX_ADDRESS_LENGTH 45 /* Max length of an (IPv6) address */ +#define MAX_PORT_LENGTH 5 /* 0-65535 */ typedef struct { const char *name; int type; @@ -31,6 +33,7 @@ typedef struct { #include "crypto.h" #include "ecdsagen.h" +#include "logger.h" #include "meshlink_internal.h" #include "netutl.h" #include "node.h" @@ -39,11 +42,16 @@ typedef struct { #include "utils.h" #include "xalloc.h" #include "ed25519/sha512.h" +#include "discovery.h" #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif +__thread meshlink_errno_t meshlink_errno; +meshlink_log_cb_t global_log_cb; +meshlink_log_level_t global_log_level; + //TODO: this can go away completely const var_t variables[] = { /* Server configuration */ @@ -111,7 +119,7 @@ const var_t variables[] = { static bool fcopy(FILE *out, const char *filename) { FILE *in = fopen(filename, "r"); if(!in) { - fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); + logger(NULL, MESHLINK_ERROR, "Could not open %s: %s\n", filename, strerror(errno)); return false; } @@ -189,10 +197,10 @@ static char *get_my_hostname(meshlink_handle_t* mesh) { goto done; // If that doesn't work, guess externally visible hostname - fprintf(stderr, "Trying to discover externally visible hostname...\n"); + logger(mesh, MESHLINK_DEBUG, "Trying to discover externally visible hostname...\n"); struct addrinfo *ai = str2addrinfo("meshlink.io", "80", SOCK_STREAM); struct addrinfo *aip = ai; - static const char request[] = "GET http://meshlink.io/host.cgi HTTP/1.0\r\n\r\n"; + static const char request[] = "GET http://www.meshlink.io/host.cgi HTTP/1.0\r\n\r\n"; while(aip) { int s = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); @@ -244,7 +252,7 @@ static char *get_my_hostname(meshlink_handle_t* mesh) { fprintf(f, "\nAddress = %s\n", hostname); fclose(f); } else { - fprintf(stderr, "Could not append Address to %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not append Address to %s: %s\n", filename, strerror(errno)); } done: @@ -278,7 +286,7 @@ static char *get_line(const char **data) { const char *end = strchr(*data, '\n'); size_t len = end ? end - *data : strlen(*data); if(len >= sizeof line) { - fprintf(stderr, "Maximum line length exceeded!\n"); + logger(NULL, MESHLINK_ERROR, "Maximum line length exceeded!\n"); return NULL; } if(len && !isprint(**data)) @@ -327,55 +335,54 @@ static bool try_bind(int port) { while(ai) { int fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP); - if(!fd) + if(!fd) { + freeaddrinfo(ai); return false; + } int result = bind(fd, ai->ai_addr, ai->ai_addrlen); closesocket(fd); - if(result) + if(result) { + freeaddrinfo(ai); return false; + } ai = ai->ai_next; } + freeaddrinfo(ai); return true; } static int check_port(meshlink_handle_t *mesh) { - if(try_bind(655)) - return 655; - - fprintf(stderr, "Warning: could not bind to port 655.\n"); - - for(int i = 0; i < 100; i++) { + for(int i = 0; i < 1000; i++) { int port = 0x1000 + (rand() & 0x7fff); if(try_bind(port)) { char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->name); FILE *f = fopen(filename, "a"); if(!f) { - fprintf(stderr, "Please change MeshLink's Port manually.\n"); + logger(mesh, MESHLINK_DEBUG, "Please change MeshLink's Port manually.\n"); return 0; } fprintf(f, "Port = %d\n", port); fclose(f); - fprintf(stderr, "MeshLink will instead listen on port %d.\n", port); return port; } } - fprintf(stderr, "Please change MeshLink's Port manually.\n"); + logger(mesh, MESHLINK_DEBUG, "Please change MeshLink's Port manually.\n"); return 0; } static bool finalize_join(meshlink_handle_t *mesh) { char *name = xstrdup(get_value(mesh->data, "Name")); if(!name) { - fprintf(stderr, "No Name found in invitation!\n"); + logger(mesh, MESHLINK_DEBUG, "No Name found in invitation!\n"); return false; } if(!check_id(name)) { - fprintf(stderr, "Invalid Name found in invitation: %s!\n", name); + logger(mesh, MESHLINK_DEBUG, "Invalid Name found in invitation: %s!\n", name); return false; } @@ -384,7 +391,7 @@ static bool finalize_join(meshlink_handle_t *mesh) { FILE *f = fopen(filename, "w"); if(!f) { - fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno)); return false; } @@ -393,7 +400,7 @@ static bool finalize_join(meshlink_handle_t *mesh) { snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name); FILE *fh = fopen(filename, "w"); if(!fh) { - fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno)); fclose(f); return false; } @@ -439,10 +446,10 @@ static bool finalize_join(meshlink_handle_t *mesh) { // Ignore unknown and unsafe variables if(!found) { - fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l); + logger(mesh, MESHLINK_DEBUG, "Ignoring unknown variable '%s' in invitation.\n", l); continue; } else if(!(variables[i].type & VAR_SAFE)) { - fprintf(stderr, "Ignoring unsafe variable '%s' in invitation.\n", l); + logger(mesh, MESHLINK_DEBUG, "Ignoring unsafe variable '%s' in invitation.\n", l); continue; } @@ -454,12 +461,12 @@ static bool finalize_join(meshlink_handle_t *mesh) { while(l && !strcasecmp(l, "Name")) { if(!check_id(value)) { - fprintf(stderr, "Invalid Name found in invitation.\n"); + logger(mesh, MESHLINK_DEBUG, "Invalid Name found in invitation.\n"); return false; } if(!strcmp(value, name)) { - fprintf(stderr, "Secondary chunk would overwrite our own host config file.\n"); + logger(mesh, MESHLINK_DEBUG, "Secondary chunk would overwrite our own host config file.\n"); return false; } @@ -467,7 +474,7 @@ static bool finalize_join(meshlink_handle_t *mesh) { f = fopen(filename, "w"); if(!f) { - fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno)); return false; } @@ -494,8 +501,10 @@ static bool finalize_join(meshlink_handle_t *mesh) { } char *b64key = ecdsa_get_base64_public_key(mesh->self->connection->ecdsa); - if(!b64key) + if(!b64key) { + fclose(fh); return false; + } fprintf(fh, "ECDSAPublicKey = %s\n", b64key); fprintf(fh, "Port = %s\n", mesh->myport); @@ -508,9 +517,9 @@ static bool finalize_join(meshlink_handle_t *mesh) { free(mesh->self->name); free(mesh->self->connection->name); mesh->self->name = xstrdup(name); - mesh->self->connection->name = xstrdup(name); + mesh->self->connection->name = name; - fprintf(stderr, "Configuration stored in: %s\n", mesh->confbase); + logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase); load_all_nodes(mesh); @@ -545,10 +554,11 @@ static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint break; case 1: + mesh->thedatalen = 0; return finalize_join(mesh); case 2: - fprintf(stderr, "Invitation succesfully accepted.\n"); + logger(mesh, MESHLINK_DEBUG, "Invitation succesfully accepted.\n"); shutdown(mesh->sock, SHUT_RDWR); mesh->success = true; break; @@ -618,12 +628,21 @@ static bool sendline(int fd, char *format, ...) { static const char *errstr[] = { [MESHLINK_OK] = "No error", + [MESHLINK_EINVAL] = "Invalid argument", [MESHLINK_ENOMEM] = "Out of memory", [MESHLINK_ENOENT] = "No such node", + [MESHLINK_EEXIST] = "Node already exists", + [MESHLINK_EINTERNAL] = "Internal error", + [MESHLINK_ERESOLV] = "Could not resolve hostname", + [MESHLINK_ESTORAGE] = "Storage error", + [MESHLINK_ENETWORK] = "Network error", + [MESHLINK_EPEER] = "Error communicating with peer", }; -const char *meshlink_strerror(meshlink_errno_t errno) { - return errstr[errno]; +const char *meshlink_strerror(meshlink_errno_t err) { + if(err < 0 || err >= sizeof errstr / sizeof *errstr) + return "Invalid error code"; + return errstr[err]; } static bool ecdsa_keygen(meshlink_handle_t *mesh) { @@ -631,39 +650,44 @@ static bool ecdsa_keygen(meshlink_handle_t *mesh) { FILE *f; char pubname[PATH_MAX], privname[PATH_MAX]; - fprintf(stderr, "Generating ECDSA keypair:\n"); + logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypair:\n"); if(!(key = ecdsa_generate())) { - fprintf(stderr, "Error during key generation!\n"); + logger(mesh, MESHLINK_DEBUG, "Error during key generation!\n"); + meshlink_errno = MESHLINK_EINTERNAL; return false; } else - fprintf(stderr, "Done.\n"); + logger(mesh, MESHLINK_DEBUG, "Done.\n"); snprintf(privname, sizeof privname, "%s" SLASH "ecdsa_key.priv", mesh->confbase); f = fopen(privname, "w"); - if(!f) + if(!f) { + meshlink_errno = MESHLINK_ESTORAGE; return false; + } #ifdef HAVE_FCHMOD fchmod(fileno(f), 0600); #endif if(!ecdsa_write_pem_private_key(key, f)) { - fprintf(stderr, "Error writing private key!\n"); + logger(mesh, MESHLINK_DEBUG, "Error writing private key!\n"); ecdsa_free(key); fclose(f); + meshlink_errno = MESHLINK_EINTERNAL; return false; } fclose(f); - snprintf(pubname, sizeof pubname, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->name); f = fopen(pubname, "a"); - if(!f) + if(!f) { + meshlink_errno = MESHLINK_ESTORAGE; return false; + } char *pubkey = ecdsa_get_base64_public_key(key); fprintf(f, "ECDSAPublicKey = %s\n", pubkey); @@ -677,7 +701,8 @@ static bool ecdsa_keygen(meshlink_handle_t *mesh) { static bool meshlink_setup(meshlink_handle_t *mesh) { if(mkdir(mesh->confbase, 0777) && errno != EEXIST) { - fprintf(stderr, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; return false; } @@ -685,67 +710,97 @@ static bool meshlink_setup(meshlink_handle_t *mesh) { snprintf(filename, sizeof filename, "%s" SLASH "hosts", mesh->confbase); if(mkdir(filename, 0777) && errno != EEXIST) { - fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; return false; } snprintf(filename, sizeof filename, "%s" SLASH "meshlink.conf", mesh->confbase); if(!access(filename, F_OK)) { - fprintf(stderr, "Configuration file %s already exists!\n", filename); + logger(mesh, MESHLINK_DEBUG, "Configuration file %s already exists!\n", filename); + meshlink_errno = MESHLINK_EEXIST; return false; } FILE *f = fopen(filename, "w"); if(!f) { - fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); - return 1; + logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return false; } fprintf(f, "Name = %s\n", mesh->name); fclose(f); - if(!ecdsa_keygen(mesh)) + if(!ecdsa_keygen(mesh)) { + meshlink_errno = MESHLINK_EINTERNAL; return false; + } check_port(mesh); return true; } -meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { +meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char* appname, dev_class_t devclass) { + return meshlink_open_with_size(confbase, name, appname, devclass, sizeof(meshlink_handle_t)); +} + +meshlink_handle_t *meshlink_open_with_size(const char *confbase, const char *name, const char* appname, dev_class_t devclass, size_t size) { + // Validate arguments provided by the application bool usingname = false; + + logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n"); if(!confbase || !*confbase) { - fprintf(stderr, "No confbase given!\n"); + logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!appname || !*appname) { + logger(NULL, MESHLINK_ERROR, "No appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; return NULL; } if(!name || !*name) { - fprintf(stderr, "No name given!\n"); + logger(NULL, MESHLINK_ERROR, "No name given!\n"); //return NULL; } else { //check name only if there is a name != NULL if(!check_id(name)) { - fprintf(stderr, "Invalid name given!\n"); + logger(NULL, MESHLINK_ERROR, "Invalid name given!\n"); + meshlink_errno = MESHLINK_EINVAL; return NULL; } else { usingname = true;} } - meshlink_handle_t *mesh = xzalloc(sizeof *mesh); + if(devclass < 0 || devclass > _DEV_CLASS_MAX) { + logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_handle_t *mesh = xzalloc(size); mesh->confbase = xstrdup(confbase); + mesh->appname = xstrdup(appname); + mesh->devclass = devclass; if (usingname) mesh->name = xstrdup(name); - pthread_mutex_init ( &(mesh->outpacketqueue_mutex), NULL); - pthread_mutex_init ( &(mesh->nodes_mutex), NULL); + + // initialize mutex + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&(mesh->mesh_mutex), &attr); + mesh->threadstarted = false; event_loop_init(&mesh->loop); mesh->loop.data = mesh; - // TODO: should be set by a function. - mesh->debug_level = 5; - // Check whether meshlink.conf already exists char filename[PATH_MAX]; @@ -754,10 +809,15 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { if(access(filename, R_OK)) { if(errno == ENOENT) { // If not, create it - meshlink_setup(mesh); + if(!meshlink_setup(mesh)) { + // meshlink_errno is set by meshlink_setup() + return NULL; + } } else { - fprintf(stderr, "Cannot not read from %s: %s\n", filename, strerror(errno)); - return meshlink_close(mesh), NULL; + logger(NULL, MESHLINK_ERROR, "Cannot not read from %s: %s\n", filename, strerror(errno)); + meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; + return NULL; } } @@ -765,8 +825,11 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { init_configuration(&mesh->config); - if(!read_server_config(mesh)) - return meshlink_close(mesh), NULL; + if(!read_server_config(mesh)) { + meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; + return NULL; + }; #ifdef HAVE_MINGW struct WSAData wsa_state; @@ -776,120 +839,227 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { // Setup up everything // TODO: we should not open listening sockets yet - if(!setup_network(mesh)) - return meshlink_close(mesh), NULL; + if(!setup_network(mesh)) { + meshlink_close(mesh); + meshlink_errno = MESHLINK_ENETWORK; + return NULL; + } + logger(NULL, MESHLINK_DEBUG, "meshlink_open returning\n"); return mesh; } -void *meshlink_main_loop(void *arg) { +static void *meshlink_main_loop(void *arg) { meshlink_handle_t *mesh = arg; + pthread_mutex_lock(&(mesh->mesh_mutex)); + try_outgoing_connections(mesh); + logger(mesh, MESHLINK_DEBUG, "Starting main_loop...\n"); main_loop(mesh); + logger(mesh, MESHLINK_DEBUG, "main_loop returned.\n"); + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } bool meshlink_start(meshlink_handle_t *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + pthread_mutex_lock(&(mesh->mesh_mutex)); + + logger(mesh, MESHLINK_DEBUG, "meshlink_start called\n"); + + mesh->thedatalen = 0; + // TODO: open listening sockets first //Check that a valid name is set if(!mesh->name ) { - fprintf(stderr, "No name given!\n"); + logger(mesh, MESHLINK_DEBUG, "No name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } // Start the main thread if(pthread_create(&mesh->thread, NULL, meshlink_main_loop, mesh) != 0) { - fprintf(stderr, "Could not start thread: %s\n", strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not start thread: %s\n", strerror(errno)); memset(&mesh->thread, 0, sizeof mesh->thread); + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } mesh->threadstarted=true; + discovery_start(mesh); + + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; } void meshlink_stop(meshlink_handle_t *mesh) { - // Shut down the listening sockets to signal the main thread to shut down - - for(int i = 0; i < mesh->listen_sockets; i++) { - shutdown(mesh->listen_socket[i].tcp.fd, SHUT_RDWR); - shutdown(mesh->listen_socket[i].udp.fd, SHUT_RDWR); + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; } - // Wait for the main thread to finish + pthread_mutex_lock(&(mesh->mesh_mutex)); + logger(mesh, MESHLINK_DEBUG, "meshlink_stop called\n"); + + // Stop discovery + discovery_stop(mesh); + // Shut down a listening socket to signal the main thread to shut down + + listen_socket_t *s = &mesh->listen_socket[0]; + shutdown(s->tcp.fd, SHUT_RDWR); + + // Wait for the main thread to finish + pthread_mutex_unlock(&(mesh->mesh_mutex)); pthread_join(mesh->thread, NULL); + pthread_mutex_lock(&(mesh->mesh_mutex)); + + mesh->threadstarted = false; + + // Fix the socket + + closesocket(s->tcp.fd); + io_del(&mesh->loop, &s->tcp); + s->tcp.fd = setup_listen_socket(&s->sa); + if(s->tcp.fd < 0) + logger(mesh, MESHLINK_ERROR, "Could not repair listenen socket!"); + else + io_add(&mesh->loop, &s->tcp, handle_new_meta_connection, s, s->tcp.fd, IO_READ); + + pthread_mutex_unlock(&(mesh->mesh_mutex)); } void meshlink_close(meshlink_handle_t *mesh) { + if(!mesh || !mesh->confbase) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + // lock is not released after this + pthread_mutex_lock(&(mesh->mesh_mutex)); + // Close and free all resources used. close_network_connections(mesh); - logger(DEBUG_ALWAYS, LOG_NOTICE, "Terminating"); + logger(mesh, MESHLINK_INFO, "Terminating"); exit_configuration(&mesh->config); event_loop_exit(&mesh->loop); - free(mesh); - #ifdef HAVE_MINGW - WSACleanup(); + if(mesh->confbase) + WSACleanup(); #endif + + ecdsa_free(mesh->invitation_key); + + free(mesh->name); + free(mesh->appname); + free(mesh->confbase); + pthread_mutex_destroy(&(mesh->mesh_mutex)); + + memset(mesh, 0, sizeof *mesh); + + free(mesh); } void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); mesh->receive_cb = cb; + pthread_mutex_unlock(&(mesh->mesh_mutex)); } void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_cb_t cb) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); mesh->node_status_cb = cb; + pthread_mutex_unlock(&(mesh->mesh_mutex)); } void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb) { - mesh->log_cb = cb; - mesh->log_level = level; + if(mesh) { + pthread_mutex_lock(&(mesh->mesh_mutex)); + mesh->log_cb = cb; + mesh->log_level = cb ? level : 0; + pthread_mutex_unlock(&(mesh->mesh_mutex)); + } else { + global_log_cb = cb; + global_log_level = cb ? level : 0; + } } -bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, unsigned int len) { +bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) { + if(!mesh || !destination) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } - /* If there is no outgoing list yet, create one. */ + if(!len) + return true; - if(!mesh->outpacketqueue) - mesh->outpacketqueue = list_alloc(NULL); + if(!data) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); //add packet to the queue outpacketqueue_t *packet_in_queue = xzalloc(sizeof *packet_in_queue); packet_in_queue->destination=destination; packet_in_queue->data=data; packet_in_queue->len=len; - pthread_mutex_lock(&(mesh->outpacketqueue_mutex)); - list_insert_head(mesh->outpacketqueue,packet_in_queue); - pthread_mutex_unlock(&(mesh->outpacketqueue_mutex)); + if(!meshlink_queue_push(&mesh->outpacketqueue, packet_in_queue)) { + free(packet_in_queue); + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return false; + } //notify event loop signal_trigger(&(mesh->loop),&(mesh->datafromapp)); + + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; } void meshlink_send_from_queue(event_loop_t* el,meshlink_handle_t *mesh) { + pthread_mutex_lock(&(mesh->mesh_mutex)); + vpn_packet_t packet; meshlink_packethdr_t *hdr = (meshlink_packethdr_t *)packet.data; - outpacketqueue_t* p = list_get_tail(mesh->outpacketqueue); - if (p) - list_delete_tail(mesh->outpacketqueue); - else return ; + outpacketqueue_t* p = meshlink_queue_pop(&mesh->outpacketqueue); + if(!p) + { + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return; + } if (sizeof(meshlink_packethdr_t) + p->len > MAXSIZE) { + pthread_mutex_unlock(&(mesh->mesh_mutex)); //log something - return ; + return; } packet.probe = false; @@ -903,62 +1073,171 @@ void meshlink_send_from_queue(event_loop_t* el,meshlink_handle_t *mesh) { mesh->self->in_packets++; mesh->self->in_bytes += packet.len; route(mesh, mesh->self, &packet); + + pthread_mutex_unlock(&(mesh->mesh_mutex)); return ; } +ssize_t meshlink_get_pmtu(meshlink_handle_t *mesh, meshlink_node_t *destination) { + if(!mesh || !destination) { + meshlink_errno = MESHLINK_EINVAL; + return -1; + } + pthread_mutex_lock(&(mesh->mesh_mutex)); + + node_t *n = (node_t *)destination; + if(!n->status.reachable) { + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return 0; + + } + else if(n->mtuprobes > 30 && n->minmtu) { + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return n->minmtu; + } + else { + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return MTU; + } +} + +char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + pthread_mutex_lock(&(mesh->mesh_mutex)); + + node_t *n = (node_t *)node; + + if(!node_read_ecdsa_public_key(mesh, n) || !n->ecdsa) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return false; + } + + char *fingerprint = ecdsa_get_base64_public_key(n->ecdsa); + + if(!fingerprint) + meshlink_errno = MESHLINK_EINTERNAL; + + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return fingerprint; +} + meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) { - return (meshlink_node_t *)lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const + if(!mesh || !name) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_node_t *node = NULL; + + pthread_mutex_lock(&(mesh->mesh_mutex)); + node = (meshlink_node_t *)lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return node; } -size_t meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t nmemb) { - size_t i = 0; +meshlink_node_t **meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t *nmemb) { + if(!mesh || !nmemb || (*nmemb && !nodes)) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_node_t **result; //lock mesh->nodes - pthread_mutex_lock(&(mesh->nodes_mutex)); + pthread_mutex_lock(&(mesh->mesh_mutex)); + + *nmemb = mesh->nodes->count; + result = realloc(nodes, *nmemb * sizeof *nodes); - for splay_each(node_t, n, mesh->nodes) { - if(i < nmemb) - nodes[i] = (meshlink_node_t *)n; - i++; + if(result) { + meshlink_node_t **p = result; + for splay_each(node_t, n, mesh->nodes) + *p++ = (meshlink_node_t *)n; + } else { + *nmemb = 0; + free(nodes); + meshlink_errno = MESHLINK_ENOMEM; } - pthread_mutex_unlock(&(mesh->nodes_mutex)); + pthread_mutex_unlock(&(mesh->mesh_mutex)); - return i; + return result; } bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void *signature, size_t *siglen) { - if(*siglen < MESHLINK_SIGLEN) + if(!mesh || !data || !len || !signature || !siglen) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(*siglen < MESHLINK_SIGLEN) { + meshlink_errno = MESHLINK_EINVAL; return false; - if(!ecdsa_sign(mesh->self->connection->ecdsa, data, len, signature)) + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + if(!ecdsa_sign(mesh->self->connection->ecdsa, data, len, signature)) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } + *siglen = MESHLINK_SIGLEN; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; } bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len, const void *signature, size_t siglen) { - if(siglen != MESHLINK_SIGLEN) + if(!mesh || !data || !len || !signature) { + meshlink_errno = MESHLINK_EINVAL; return false; + } + + if(siglen != MESHLINK_SIGLEN) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + bool rval = false; + struct node_t *n = (struct node_t *)source; node_read_ecdsa_public_key(mesh, n); - if(!n->ecdsa) - return false; - return ecdsa_verify(((struct node_t *)source)->ecdsa, data, len, signature); + if(!n->ecdsa) { + meshlink_errno = MESHLINK_EINTERNAL; + rval = false; + } else { + rval = ecdsa_verify(((struct node_t *)source)->ecdsa, data, len, signature); + } + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return rval; } static bool refresh_invitation_key(meshlink_handle_t *mesh) { char filename[PATH_MAX]; + + pthread_mutex_lock(&(mesh->mesh_mutex)); snprintf(filename, sizeof filename, "%s" SLASH "invitations", mesh->confbase); if(mkdir(filename, 0700) && errno != EEXIST) { - fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } // Count the number of valid invitations, clean up old ones DIR *dir = opendir(filename); if(!dir) { - fprintf(stderr, "Could not read directory %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } @@ -979,14 +1258,16 @@ static bool refresh_invitation_key(meshlink_handle_t *mesh) { else unlink(invname); } else { - fprintf(stderr, "Could not stat %s: %s\n", invname, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno)); errno = 0; } } if(errno) { - fprintf(stderr, "Error while reading directory %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", filename, strerror(errno)); closedir(dir); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } @@ -1003,25 +1284,33 @@ static bool refresh_invitation_key(meshlink_handle_t *mesh) { } } - if(mesh->invitation_key) + if(mesh->invitation_key) { + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; + } // Create a new key if necessary. FILE *f = fopen(filename, "r"); if(!f) { if(errno != ENOENT) { - fprintf(stderr, "Could not read %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not read %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } mesh->invitation_key = ecdsa_generate(); if(!mesh->invitation_key) { - fprintf(stderr, "Could not generate a new key!\n"); + logger(mesh, MESHLINK_DEBUG, "Could not generate a new key!\n"); + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } f = fopen(filename, "w"); if(!f) { - fprintf(stderr, "Could not write %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not write %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } chmod(filename, 0600); @@ -1030,28 +1319,53 @@ static bool refresh_invitation_key(meshlink_handle_t *mesh) { } else { mesh->invitation_key = ecdsa_read_pem_private_key(f); fclose(f); - if(!mesh->invitation_key) - fprintf(stderr, "Could not read private key from %s\n", filename); + if(!mesh->invitation_key) { + logger(mesh, MESHLINK_DEBUG, "Could not read private key from %s\n", filename); + meshlink_errno = MESHLINK_ESTORAGE; + } } + pthread_mutex_unlock(&(mesh->mesh_mutex)); return mesh->invitation_key; } bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) { + if(!mesh || !address) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + bool rval = false; + + pthread_mutex_lock(&(mesh->mesh_mutex)); + for(const char *p = address; *p; p++) { if(isalnum(*p) || *p == '-' || *p == '.' || *p == ':') continue; - fprintf(stderr, "Invalid character in address: %s\n", address); + logger(mesh, MESHLINK_DEBUG, "Invalid character in address: %s\n", address); + meshlink_errno = MESHLINK_EINVAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } - return append_config_file(mesh, mesh->self->name, "Address", address); + rval = append_config_file(mesh, mesh->self->name, "Address", address); + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return rval; } char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + // Check validity of the new node's name if(!check_id(name)) { - fprintf(stderr, "Invalid name for node.\n"); + logger(mesh, MESHLINK_DEBUG, "Invalid name for node.\n"); + meshlink_errno = MESHLINK_EINVAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } @@ -1059,25 +1373,34 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name); if(!access(filename, F_OK)) { - fprintf(stderr, "A host config file for %s already exists!\n", name); + logger(mesh, MESHLINK_DEBUG, "A host config file for %s already exists!\n", name); + meshlink_errno = MESHLINK_EEXIST; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } // Ensure no other nodes know about this name if(meshlink_get_node(mesh, name)) { - fprintf(stderr, "A node with name %s is already known!\n", name); + logger(mesh, MESHLINK_DEBUG, "A node with name %s is already known!\n", name); + meshlink_errno = MESHLINK_EEXIST; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } // Get the local address char *address = get_my_hostname(mesh); if(!address) { - fprintf(stderr, "No Address known for ourselves!\n"); + logger(mesh, MESHLINK_DEBUG, "No Address known for ourselves!\n"); + meshlink_errno = MESHLINK_ERESOLV; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } - if(!refresh_invitation_key(mesh)) + if(!refresh_invitation_key(mesh)) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; + } char hash[64]; @@ -1100,11 +1423,15 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { b64encode_urlsafe(cookie, cookie, 18); + free(fingerprint); + // Create a file containing the details of the invitation. snprintf(filename, sizeof filename, "%s" SLASH "invitations" SLASH "%s", mesh->confbase, cookiehash); int ifd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600); if(!ifd) { - fprintf(stderr, "Could not create invitation file %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } FILE *f = fdopen(ifd, "w"); @@ -1133,7 +1460,9 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { } fclose(tc); } else { - fprintf(stderr, "Could not create %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } @@ -1147,11 +1476,20 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { // Create an URL from the local address, key hash and cookie char *url; xasprintf(&url, "%s/%s%s", address, hash, cookie); + free(address); + pthread_mutex_unlock(&(mesh->mesh_mutex)); return url; } bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { + if(!mesh || !invitation) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + //TODO: think of a better name for this variable, or of a different way to tokenize the invitation URL. char copy[strlen(invitation) + 1]; strcpy(copy, invitation); @@ -1184,15 +1522,18 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { } if(!port) - port = "655"; + goto invalid; if(!b64decode(slash, mesh->hash, 18) || !b64decode(slash + 24, mesh->cookie, 18)) goto invalid; // Generate a throw-away key for the invitation. ecdsa_t *key = ecdsa_generate(); - if(!key) + if(!key) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } char *b64key = ecdsa_get_base64_public_key(key); @@ -1203,39 +1544,56 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { // Connect to the meshlink daemon mentioned in the URL. struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM); - if(!ai) + if(!ai) { + meshlink_errno = MESHLINK_ERESOLV; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } mesh->sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if(mesh->sock <= 0) { - fprintf(stderr, "Could not open socket: %s\n", strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno)); + freeaddrinfo(ai); + meshlink_errno = MESHLINK_ENETWORK; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } if(connect(mesh->sock, ai->ai_addr, ai->ai_addrlen)) { - fprintf(stderr, "Could not connect to %s port %s: %s\n", address, port, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not connect to %s port %s: %s\n", address, port, strerror(errno)); closesocket(mesh->sock); + freeaddrinfo(ai); + meshlink_errno = MESHLINK_ENETWORK; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } - fprintf(stderr, "Connected to %s port %s...\n", address, port); + freeaddrinfo(ai); + + logger(mesh, MESHLINK_DEBUG, "Connected to %s port %s...\n", address, port); // Tell him we have an invitation, and give him our throw-away key. mesh->blen = 0; if(!sendline(mesh->sock, "0 ?%s %d.%d", b64key, PROT_MAJOR, 1)) { - fprintf(stderr, "Error sending request to %s port %s: %s\n", address, port, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Error sending request to %s port %s: %s\n", address, port, strerror(errno)); closesocket(mesh->sock); + meshlink_errno = MESHLINK_ENETWORK; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } + free(b64key); + char hisname[4096] = ""; int code, hismajor, hisminor = 0; if(!recvline(mesh, sizeof mesh->line) || sscanf(mesh->line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(mesh, sizeof mesh->line) || !rstrip(mesh->line) || sscanf(mesh->line, "%d ", &code) != 1 || code != ACK || strlen(mesh->line) < 3) { - fprintf(stderr, "Cannot read greeting from peer\n"); + logger(mesh, MESHLINK_DEBUG, "Cannot read greeting from peer\n"); closesocket(mesh->sock); + meshlink_errno = MESHLINK_ENETWORK; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } @@ -1243,26 +1601,39 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { char *fingerprint = mesh->line + 2; char hishash[64]; if(sha512(fingerprint, strlen(fingerprint), hishash)) { - fprintf(stderr, "Could not create hash\n%s\n", mesh->line + 2); + logger(mesh, MESHLINK_DEBUG, "Could not create hash\n%s\n", mesh->line + 2); + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } if(memcmp(hishash, mesh->hash, 18)) { - fprintf(stderr, "Peer has an invalid key!\n%s\n", mesh->line + 2); + logger(mesh, MESHLINK_DEBUG, "Peer has an invalid key!\n%s\n", mesh->line + 2); + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint); - if(!hiskey) + if(!hiskey) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } // Start an SPTPS session - if(!sptps_start(&mesh->sptps, mesh, true, false, key, hiskey, "meshlink invitation", 15, invitation_send, invitation_receive)) + if(!sptps_start(&mesh->sptps, mesh, true, false, key, hiskey, "meshlink invitation", 15, invitation_send, invitation_receive)) { + meshlink_errno = MESHLINK_EINTERNAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } // Feed rest of input buffer to SPTPS - if(!sptps_receive_data(&mesh->sptps, mesh->buffer, mesh->blen)) + if(!sptps_receive_data(&mesh->sptps, mesh->buffer, mesh->blen)) { + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } int len; @@ -1270,12 +1641,17 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { if(len < 0) { if(errno == EINTR) continue; - fprintf(stderr, "Error reading data from %s port %s: %s\n", address, port, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Error reading data from %s port %s: %s\n", address, port, strerror(errno)); + meshlink_errno = MESHLINK_ENETWORK; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } - if(!sptps_receive_data(&mesh->sptps, mesh->line, len)) + if(!sptps_receive_data(&mesh->sptps, mesh->line, len)) { + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; + } } sptps_stop(&mesh->sptps); @@ -1284,23 +1660,37 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { closesocket(mesh->sock); if(!mesh->success) { - fprintf(stderr, "Connection closed by peer, invitation cancelled.\n"); + logger(mesh, MESHLINK_DEBUG, "Connection closed by peer, invitation cancelled.\n"); + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; invalid: - fprintf(stderr, "Invalid invitation URL or you are already connected to a Mesh ?\n"); + logger(mesh, MESHLINK_DEBUG, "Invalid invitation URL or you are already connected to a Mesh ?\n"); + meshlink_errno = MESHLINK_EINVAL; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } char *meshlink_export(meshlink_handle_t *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->self->name); FILE *f = fopen(filename, "r"); if(!f) { - fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not open %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } @@ -1312,25 +1702,40 @@ char *meshlink_export(meshlink_handle_t *mesh) { char *buf = xmalloc(len); snprintf(buf, len, "Name = %s\n", mesh->self->name); if(fread(buf + len - fsize - 1, fsize, 1, f) != 1) { - fprintf(stderr, "Error reading from %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Error reading from %s: %s\n", filename, strerror(errno)); fclose(f); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return NULL; } fclose(f); buf[len - 1] = 0; + + pthread_mutex_unlock(&(mesh->mesh_mutex)); return buf; } bool meshlink_import(meshlink_handle_t *mesh, const char *data) { + if(!mesh || !data) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + if(strncmp(data, "Name = ", 7)) { - fprintf(stderr, "Invalid data\n"); + logger(mesh, MESHLINK_DEBUG, "Invalid data\n"); + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } char *end = strchr(data + 7, '\n'); if(!end) { - fprintf(stderr, "Invalid data\n"); + logger(mesh, MESHLINK_DEBUG, "Invalid data\n"); + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } @@ -1339,25 +1744,33 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { memcpy(name, data + 7, len); name[len] = 0; if(!check_id(name)) { - fprintf(stderr, "Invalid Name\n"); + logger(mesh, MESHLINK_DEBUG, "Invalid Name\n"); + meshlink_errno = MESHLINK_EPEER; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name); if(!access(filename, F_OK)) { - fprintf(stderr, "File %s already exists, not importing\n", filename); + logger(mesh, MESHLINK_DEBUG, "File %s already exists, not importing\n", filename); + meshlink_errno = MESHLINK_EEXIST; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } if(errno != ENOENT) { - fprintf(stderr, "Error accessing %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Error accessing %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } FILE *f = fopen(filename, "w"); if(!f) { - fprintf(stderr, "Could not create %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_DEBUG, "Could not create %s: %s\n", filename, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + pthread_mutex_unlock(&(mesh->mesh_mutex)); return false; } @@ -1366,19 +1779,139 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { load_all_nodes(mesh); + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; } void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) { - node_t *n; - n = (node_t*)node; - n->status.blacklisted=true; - fprintf(stderr, "Blacklisted %s.\n",node->name); + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + node_t *n; + n = (node_t*)node; + n->status.blacklisted=true; + logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n",node->name); //Make blacklisting persistent in the config file append_config_file(mesh, n->name, "blacklisted", "yes"); - return; + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return; +} + +void meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) { + if(!mesh || !node) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + node_t *n = (node_t *)node; + n->status.blacklisted = false; + + //TODO: remove blacklisted = yes from the config file + + pthread_mutex_unlock(&(mesh->mesh_mutex)); + return; +} + +/* Hint that a hostname may be found at an address + * See header file for detailed comment. + */ +void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const struct sockaddr *addr) { + if(!mesh || !node || !addr) + return; + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + char *host = NULL, *port = NULL, *str = NULL; + sockaddr2str((const sockaddr_t *)addr, &host, &port); + + if(host && port) { + xasprintf(&str, "%s %s", host, port); + if ( (strncmp ("fe80",host,4) != 0) && ( strncmp("127.",host,4) != 0 ) && ( strcmp("localhost",host) !=0 ) ) + append_config_file(mesh, node->name, "Address", str); + else + logger(mesh, MESHLINK_DEBUG, "Not adding Link Local IPv6 Address to config\n"); + } + + free(str); + free(host); + free(port); + + pthread_mutex_unlock(&(mesh->mesh_mutex)); + // @TODO do we want to fire off a connection attempt right away? +} + +/* Return an array of edges in the current network graph. + * Data captures the current state and will not be updated. + * Caller must deallocate data when done. + */ +meshlink_edge_t **meshlink_get_all_edges_state(meshlink_handle_t *mesh, meshlink_edge_t **edges, size_t *nmemb) { + if(!mesh || !nmemb || (*nmemb && !edges)) { + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + + meshlink_edge_t **result = NULL; + meshlink_edge_t *copy = NULL; + int result_size = 0; + + result_size = mesh->edges->count; + + // if result is smaller than edges, we have to dealloc all the excess meshlink_edge_t + if(result_size > *nmemb) { + result = realloc(edges, result_size * sizeof (meshlink_edge_t*)); + } else { + result = edges; + } + + if(result) { + meshlink_edge_t **p = result; + int n = 0; + for splay_each(edge_t, e, mesh->edges) { + // skip edges that do not represent a two-directional connection + if((!e->reverse) || (e->reverse->to != e->from)) { + result_size--; + continue; + } + n++; + // the first *nmemb members of result can be re-used + if(n > *nmemb) { + copy = xzalloc(sizeof *copy); + } + else { + copy = *p; + } + copy->from = (meshlink_node_t*)e->from; + copy->to = (meshlink_node_t*)e->to; + copy->address = e->address.storage; + copy->options = e->options; + copy->weight = e->weight; + *p++ = copy; + } + // shrink result to the actual amount of memory used + for(int i = *nmemb; i > result_size; i--) { + free(result[i - 1]); + } + result = realloc(result, result_size * sizeof (meshlink_edge_t*)); + *nmemb = result_size; + } else { + *nmemb = 0; + free(result); + meshlink_errno = MESHLINK_ENOMEM; + } + + pthread_mutex_unlock(&(mesh->mesh_mutex)); + + return result; } static bool channel_pre_accept(struct utcp *utcp, uint16_t port) { @@ -1458,3 +1991,12 @@ static void __attribute__((constructor)) meshlink_init(void) { static void __attribute__((destructor)) meshlink_exit(void) { crypto_exit(); } + + +/// Device class traits +dev_class_traits_t dev_class_traits[_DEV_CLASS_MAX +1] = { + { .min_connects = 3, .max_connects = 10000, .edge_weight = 1 }, // DEV_CLASS_BACKBONE + { .min_connects = 3, .max_connects = 100, .edge_weight = 3 }, // DEV_CLASS_STATIONARY + { .min_connects = 3, .max_connects = 3, .edge_weight = 6 }, // DEV_CLASS_PORTABLE + { .min_connects = 1, .max_connects = 1, .edge_weight = 9 }, // DEV_CLASS_UNKNOWN +}; diff --git a/src/meshlink.h b/src/meshlink.h index b54e961e..3d929073 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -25,12 +25,22 @@ #include #include +#if defined(_WIN32) +#include +#else +#include +#include +#endif + #ifdef __cplusplus extern "C" { #endif /// The length in bytes of a signature made with meshlink_sign() -#define MESHLINK_SIGLEN 64 +#define MESHLINK_SIGLEN (64) + +// The maximum length of fingerprints +#define MESHLINK_FINGERPRINTLEN (64) /// A handle for an instance of MeshLink. typedef struct meshlink_handle meshlink_handle_t; @@ -38,26 +48,57 @@ typedef struct meshlink_handle meshlink_handle_t; /// A handle for a MeshLink node. typedef struct meshlink_node meshlink_node_t; +/// A handle for a MeshLink edge. +typedef struct meshlink_edge meshlink_edge_t; + /// A handle for a MeshLink channel. typedef struct meshlink_channel meshlink_channel_t; /// Code of most recent error encountered. typedef enum { MESHLINK_OK, ///< Everything is fine + MESHLINK_EINVAL, ///< Invalid parameter(s) to function call MESHLINK_ENOMEM, ///< Out of memory MESHLINK_ENOENT, ///< Node is not known + MESHLINK_EEXIST, ///< Node already exists + MESHLINK_EINTERNAL, ///< MeshLink internal error + MESHLINK_ERESOLV, ///< MeshLink could not resolve a hostname + MESHLINK_ESTORAGE, ///< MeshLink coud not load or write data from/to disk + MESHLINK_ENETWORK, ///< MeshLink encountered a network error + MESHLINK_EPEER, ///< A peer caused an error } meshlink_errno_t; +/// Device class +typedef enum { + DEV_CLASS_BACKBONE = 0, + DEV_CLASS_STATIONARY = 1, + DEV_CLASS_PORTABLE = 2, + DEV_CLASS_UNKNOWN = 3, + _DEV_CLASS_MAX = 3 +} dev_class_t; + +/// A variable holding the last encountered error from MeshLink. +/** This is a thread local variable that contains the error code of the most recent error + * encountered by a MeshLink API function called in the current thread. + * The variable is only updated when an error is encountered, and is not reset to MESHLINK_OK + * if a function returned succesfully. + */ +extern __thread meshlink_errno_t meshlink_errno; + #ifndef MESHLINK_INTERNAL_H struct meshlink_handle { - meshlink_errno_t meshlink_errno; ///< Code of the last encountered error. - const char *errstr; ///< Textual representation of most recent error encountered. + char *name; + char *appname; + dev_class_t devclass; + void *priv; }; struct meshlink_node { - const char *name; ///< Textual name of this node. - void *priv; ///< Private pointer which the application can set at will. + char *name; ///< Textual name of this node. It is stored in a nul-terminated C string, which is allocated by MeshLink. + uint32_t options; + dev_class_t devclass; + void *priv; ///< Private pointer which may be set freely by the application, and is never used or modified by MeshLink. }; struct meshlink_channel { @@ -65,39 +106,74 @@ struct meshlink_channel { #endif // MESHLINK_INTERNAL_H +/// An edge in the meshlink network. +struct meshlink_edge { + struct meshlink_node *from; ///< Pointer to a node. Node memory is + // owned by meshlink and should not be + // deallocated. Node contents may be + // changed by meshlink. + struct meshlink_node *to; ///< Pointer to a node. Node memory is + // owned by meshlink and should not be + // deallocated. Node contents may be + // changed by meshlink. + struct sockaddr_storage address;///< The address information associated + // with this edge. + uint32_t options; ///< Edge options. @TODO what are edge options? + int weight; ///< Weight assigned to this edge. +}; + /// Get the text for the given MeshLink error code. /** This function returns a pointer to the string containing the description of the given error code. * - * @param errno An error code returned by MeshLink. + * @param err An error code returned by MeshLink. * * @return A pointer to a string containing the description of the error code. + * The pointer is to static storage that is valid for the lifetime of the application. + * This function will always return a valid pointer, even if an invalid error code has been passed. */ -extern const char *meshlink_strerror(meshlink_errno_t errno); +extern const char *meshlink_strerror(meshlink_errno_t err); -/// Initialize MeshLink's configuration directory. -/** This function causes MeshLink to initialize its configuration directory, - * if it hasn't already been initialized. - * It only has to be run the first time the application starts, - * but it is not a problem if it is run more than once, as long as - * the arguments given are the same. +/// Open or create a MeshLink instance. +/** This function opens or creates a MeshLink instance. + * The state is stored in the configuration directory passed in the variable @a confbase @a. + * If the configuration directory does not exist yet, for example when it is the first time + * this instance is opened, the configuration directory will be automatically created and initialized. + * However, the parent directory should already exist, otherwise an error will be returned. + * + * The name given should be a unique identifier for this instance. + * + * This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink. + * When the application does no longer need to use this handle, it must call meshlink_close() to + * free its resources. * * This function does not start any network I/O yet. The application should * first set callbacks, and then call meshlink_start(). * * @param confbase The directory in which MeshLink will store its configuration files. + * After the function returns, the application is free to overwrite or free @a confbase @a. * @param name The name which this instance of the application will use in the mesh. + * After the function returns, the application is free to overwrite or free @a name @a. + * @param appname The application name which will be used in the mesh. + * After the function returns, the application is free to overwrite or free @a name @a. + * @param devclass The device class which will be used in the mesh. * - * @return This function will return true if MeshLink has succesfully set up its configuration files, false otherwise. + * @return A pointer to a meshlink_handle_t which represents this instance of MeshLink, or NULL in case of an error. + * The pointer is valid until meshlink_close() is called. */ -extern meshlink_handle_t *meshlink_open(const char *confbase, const char *name); +extern meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char* appname, dev_class_t devclass); + +/// is used by the C++ wrapper to allocate more memory behind the handle +extern meshlink_handle_t *meshlink_open_with_size(const char *confbase, const char *name, const char* appname, dev_class_t devclass, size_t size); /// Start MeshLink. /** This function causes MeshLink to open network sockets, make outgoing connections, and * create a new thread, which will handle all network I/O. * + * It is allowed to call this function even if MeshLink is already started, in which case it will return true. + * * @param mesh A handle which represents an instance of MeshLink. * - * @return This function will return true if MeshLink has succesfully started its thread, false otherwise. + * @return This function will return true if MeshLink has succesfully started, false otherwise. */ extern bool meshlink_start(meshlink_handle_t *mesh); @@ -105,14 +181,20 @@ extern bool meshlink_start(meshlink_handle_t *mesh); /** This function causes MeshLink to disconnect from all other nodes, * close all sockets, and shut down its own thread. * + * This function always succeeds. It is allowed to call meshlink_stop() even if MeshLink is already stopped or has never been started. + * * @param mesh A handle which represents an instance of MeshLink. */ extern void meshlink_stop(meshlink_handle_t *mesh); /// Close the MeshLink handle. /** This function calls meshlink_stop() if necessary, - * and frees all memory allocated by MeshLink. - * Afterwards, the handle and any pointers to a struct meshlink_node are invalid. + * and frees the struct meshlink_handle and all associacted memory allocated by MeshLink. + * Afterwards, the handle and any pointers to a struct meshlink_node or struct meshlink_channel are invalid. + * + * It is allowed to call this function at any time on a valid handle, except inside callback functions. + * If called at a proper time with a valid handle, this function always succeeds. + * If called within a callback or with an invalid handle, the result is undefined. * * @param mesh A handle which represents an instance of MeshLink. */ @@ -121,8 +203,10 @@ extern void meshlink_close(meshlink_handle_t *mesh); /// A callback for receiving data from the mesh. /** @param mesh A handle which represents an instance of MeshLink. * @param source A pointer to a meshlink_node_t describing the source of the data. - * @param data A pointer to a buffer containing the data sent by the source. - * @param len The length of the received data. + * @param data A pointer to a buffer containing the data sent by the source, or NULL in case there is no data (an empty packet was received). + * The pointer is only valid during the lifetime of the callback. + * The callback should mempcy() the data if it needs to be available outside the callback. + * @param len The length of the received data, or 0 in case there is no data. */ typedef void (*meshlink_receive_cb_t)(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len); @@ -135,12 +219,14 @@ typedef void (*meshlink_receive_cb_t)(meshlink_handle_t *mesh, meshlink_node_t * * * @param mesh A handle which represents an instance of MeshLink. * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled. */ extern void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb); /// A callback reporting node status changes. /** @param mesh A handle which represents an instance of MeshLink. * @param node A pointer to a meshlink_node_t describing the node whose status changed. + * This pointer is valid until meshlink_close() is called. * @param reachable True if the node is reachable, false otherwise. */ typedef void (*meshlink_node_status_cb_t)(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable); @@ -154,6 +240,7 @@ typedef void (*meshlink_node_status_cb_t)(meshlink_handle_t *mesh, meshlink_node * * @param mesh A handle which represents an instance of MeshLink. * @param cb A pointer to the function which will be called when another node's status changes. + * If a NULL pointer is given, the callback will be disabled. */ extern void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_cb_t cb); @@ -167,22 +254,35 @@ typedef enum { } meshlink_log_level_t; /// A callback for receiving log messages generated by MeshLink. -/** @param mesh A handle which represents an instance of MeshLink. +/** @param mesh A handle which represents an instance of MeshLink, or NULL. * @param level An enum describing the severity level of the message. - * @param text A pointer to a string containing the textual log message. + * @param text A pointer to a nul-terminated C string containing the textual log message. + * This pointer is only valid for the duration of the callback. + * The application must not free() this pointer. + * The application should strdup() the text if it has to be available outside the callback. */ typedef void (*meshlink_log_cb_t)(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text); /// Set the log callback. /** This functions sets the callback that is called whenever MeshLink has some information to log. - * The callback is run in MeshLink's own thread. + * + * The @a mesh @a parameter can either be a valid MeshLink handle, or NULL. + * In case it is NULL, the callback will be called for errors that happen outside the context of a valid mesh instance. + * Otherwise, it will be called for errors that happen in the context of the given mesh instance. + * + * If @a mesh @a is not NULL, then the callback is run in MeshLink's own thread. * It is important that the callback uses apprioriate methods (queues, pipes, locking, etc.) * to hand the data over to the application's thread. * The callback should also not block itself and return as quickly as possible. * - * @param mesh A handle which represents an instance of MeshLink. + * The @a mesh @a parameter can either be a valid MeshLink handle, or NULL. + * In case it is NULL, the callback will be called for errors that happen outside the context of a valid mesh instance. + * Otherwise, it will be called for errors that happen in the context of the given mesh instance. + * + * @param mesh A handle which represents an instance of MeshLink, or NULL. * @param level An enum describing the minimum severity level. Debugging information with a lower level will not trigger the callback. * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled. */ extern void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb); @@ -191,38 +291,79 @@ extern void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t le * The packet is sent using UDP semantics, which means that * the packet is sent as one unit and is received as one unit, * and that there is no guarantee that the packet will arrive at the destination. + * Packets that are too big to be sent over the network as one unit might be dropped, and this function may return an error if this situation can be detected beforehand. + * The application should not send packets that are larger than the path MTU, which can be queried with meshlink_get_pmtu(). * The application should take care of getting an acknowledgement and retransmission if necessary. * * @param mesh A handle which represents an instance of MeshLink. * @param destination A pointer to a meshlink_node_t describing the destination for the data. * @param data A pointer to a buffer containing the data to be sent to the source. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * It is valid to specify a NULL pointer, but only if @a len @a is also 0. * @param len The length of the data. * @return This function will return true if MeshLink has queued the message for transmission, and false otherwise. * A return value of true does not guarantee that the message will actually arrive at the destination. */ -extern bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, unsigned int len); +extern bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len); + +/// Query the maximum packet size that can be sent to a node. +/** This functions returns the maximum size of packets (path MTU) that can be sent to a specific node with meshlink_send(). + * The path MTU is a property of the path packets will take to the destination node over the Internet. + * It can be different for different destination nodes. + * and the path MTU can change at any point in time due to changes in the Internet. + * Therefore, although this should only occur rarely, it can still happen that packets that do not exceed this size get dropped. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param destination A pointer to a meshlink_node_t describing the destination for the data. + * + * @return The recommended maximum size of packets that are to be sent to the destination node, 0 if the node is unreachable, + * or a negative value in case of an error. + */ +extern ssize_t meshlink_get_pmtu(meshlink_handle_t *mesh, meshlink_node_t *destination); /// Get a handle for a specific node. /** This function returns a handle for the node with the given name. * * @param mesh A handle which represents an instance of MeshLink. * @param name The name of the node for which a handle is requested. + * After this function returns, the application is free to overwrite or free @a name @a. * * @return A pointer to a meshlink_node_t which represents the requested node, * or NULL if the requested node does not exist. + * The pointer is guaranteed to be valid until meshlink_close() is called. */ extern meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name); +/// Get the fingerprint of a node's public key. +/** This function returns a fingerprint of the node's public key. + * It should be treated as an opaque blob. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t describing the node. + * + * @return A nul-terminated C string containing the fingerprint of the node's public key in a printable ASCII format. + * The application should call free() after it is done using this string. + */ +extern char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node); + /// Get a list of all nodes. /** This function returns a list with handles for all known nodes. * * @param mesh A handle which represents an instance of MeshLink. - * @param nodes A pointer to an array of pointers to meshlink_node_t, which should be allocated by the application. - * @param nmemb The maximum number of pointers that can be stored in the nodes array. - * - * @return The number of known nodes. This can be larger than nmemb, in which case not all nodes were stored in the nodes array. + * @param nodes A pointer to a previously allocated array of pointers to meshlink_node_t, 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 are stored in the array. + * In case the @a nodes @a 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, or NULL in case of an error. + * If the @a nodes @a 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 @a should not be used anymore. + * If the new value is NULL, then the old array will have been freed by MeshLink. */ -extern size_t meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t nmemb); +extern meshlink_node_t **meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t *nmemb); /// Sign data using the local node's MeshLink key. /** This function signs data using the local node's MeshLink key. @@ -232,6 +373,8 @@ extern size_t meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t ** * @param data A pointer to a buffer containing the data to be signed. * @param len The length of the data to be signed. * @param signature A pointer to a buffer where the signature will be stored. + * The buffer must be allocated by the application, and should be at least MESHLINK_SIGLEN bytes big. + * The signature is a binary blob, and is not nul-terminated. * @param siglen The size of the signature buffer. Will be changed after the call to match the size of the signature itself. * * @return This function returns true if the signature was correctly generated, false otherwise. @@ -245,8 +388,9 @@ extern bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, * @param source A pointer to a meshlink_node_t describing the source of the signature. * @param data A pointer to a buffer containing the data to be verified. * @param len The length of the data to be verified. - * @param signature A pointer to a string containing the signature. - * @param siglen The size of the signature. + * @param signature A pointer to a buffer where the signature is stored. + * @param siglen A pointer to a variable holding the size of the signature buffer. + * The contents of the variable will be changed by meshlink_sign() to reflect the actual size of the signature. * * @return This function returns true if the signature is valid, false otherwise. */ @@ -256,7 +400,7 @@ extern bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, co /** This function adds an Address for the local node, which will be used for invitation URLs. * * @param mesh A handle which represents an instance of MeshLink. - * @param address A string containing the address, which can be either in numeric format or a hostname. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. * * @return This function returns true if the address was added, false otherwise. */ @@ -269,9 +413,10 @@ extern bool meshlink_add_address(meshlink_handle_t *mesh, const char *address); * The URL can only be used once, after the user has joined the mesh the URL is no longer valid. * * @param mesh A handle which represents an instance of MeshLink. - * @param name The name that the invitee will use in the mesh. + * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh. + * After this function returns, the application is free to overwrite or free @a name @a. * - * @return This function returns a string that contains the invitation URL. + * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error. * The application should call free() after it has finished using the URL. */ extern char *meshlink_invite(meshlink_handle_t *mesh, const char *name); @@ -282,7 +427,8 @@ extern char *meshlink_invite(meshlink_handle_t *mesh, const char *name); * After a succesfully accepted invitation, the name of the local node may have changed. * * @param mesh A handle which represents an instance of MeshLink. - * @param invitation A string containing the invitation URL. + * @param invitation A nul-terminated C string containing the invitation URL. + * After this function returns, the application is free to overwrite or free @a invitation @a. * * @return This function returns true if the local node joined the mesh it was invited to, false otherwise. */ @@ -292,10 +438,13 @@ extern bool meshlink_join(meshlink_handle_t *mesh, const char *invitation); /** This function generates a string that contains the local node's public key and one or more IP addresses. * The application can pass it in some way to another node, which can then import it, * granting the local node access to the other node's mesh. + * The exported data does not contain any secret keys, it is therefore safe to transmit this data unencrypted over public networks. + * + * Note that to create a working connection between two nodes, both must call meshink_export() and both must meshlink_import() each other's data. * * @param mesh A handle which represents an instance of MeshLink. * - * @return This function returns a string that contains the exported key and addresses. + * @return This function returns a nul-terminated C string that contains the exported key and addresses, or NULL in case of an error. * The application should call free() after it has finished using this string. */ extern char *meshlink_export(meshlink_handle_t *mesh); @@ -303,9 +452,13 @@ extern char *meshlink_export(meshlink_handle_t *mesh); /// Import another node's key and addresses. /** This function accepts a string containing the exported public key and addresses of another node. * By importing this data, the local node grants the other node access to its mesh. + * The application should make sure that the data it imports is really coming from the node it wants to import, + * + * Note that to create a working connection between two nodes, both must call meshink_export() and both must meshlink_import() each other's data. * * @param mesh A handle which represents an instance of MeshLink. - * @param data A string containing the other node's exported key and addresses. + * @param data A nul-terminated C string containing the other node's exported key and addresses. + * After this function returns, the application is free to overwrite or free @a data @a. * * @return This function returns true if the data was valid and the other node has been granted access to the mesh, false otherwise. */ @@ -321,16 +474,38 @@ extern bool meshlink_import(meshlink_handle_t *mesh, const char *data); */ extern void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node); +/// Whitelist a node on the mesh. +/** This function causes the local node to whitelist a previously blacklisted node. + * The local node will allow connections to and from that node, + * and will send data to it and accept any data received from it. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t describing the node to be blacklisted. + */ +extern void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node); + /// A callback for accepting incoming channels. /** This function is called whenever a remote node wants to open a channel to the local node. * The application then has to decide whether to accept or reject this channel. * + * The callback is run in MeshLink's own thread. + * It is therefore important that the callback return quickly and uses apprioriate methods (queues, pipes, locking, etc.) + * to hand any data over to the application's thread. + * * @param mesh A handle which represents an instance of MeshLink. * @param channel A handle for the incoming channel. + * If the application accepts the incoming channel by returning true, + * then this handle is valid until meshlink_channel_close() is called on it. + * If the application rejects the incoming channel by returning false, + * then this handle is invalid after the callback returns + * (the callback does not need to call meshlink_channel_close() itself in this case). * @param node The node from which this channel is being initiated. + * The pointer is guaranteed to be valid until meshlink_close() is called. * @param port The port number the peer wishes to connect to. - * @param data A pointer to a buffer containing data already received. (Not yet used.) - * @param len The length of the data. (Not yet used.) + * @param data A pointer to a buffer containing data already received, or NULL in case no data has been received yet. (Not yet used.) + * The pointer is only valid during the lifetime of the callback. + * The callback should mempcy() the data if it needs to be available outside the callback. + * @param len The length of the data, or 0 in case no data has been received yet. (Not yet used.) * * @return This function should return true if the application accepts the incoming channel, false otherwise. * If returning false, the channel is invalid and may not be used anymore. @@ -338,13 +513,18 @@ extern void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node); typedef bool (*meshlink_channel_accept_cb_t)(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_node_t *node, uint16_t port, const void *data, size_t len); /// A callback for receiving data from a channel. -/** This function is called whenever a remote node wants to open a channel to the local node. - * The application then has to decide whether to accept or reject this channel. +/** This function is called whenever data is received from a remote node on a channel. + * + * This function is also called in case the channel has been closed by the remote node, or when the channel is terminated abnormally. + * In both cases, @a data @a will be NULL and @a len @a will be 0, and meshlink_errno will be set. + * In any case, the @a channel @a handle will still be valid until the application calls meshlink_close(). * * @param mesh A handle which represents an instance of MeshLink. * @param channel A handle for the channel. - * @param data A pointer to a buffer containing data sent by the source. - * @param len The length of the data. + * @param data A pointer to a buffer containing data sent by the source, or NULL in case of an error. + * The pointer is only valid during the lifetime of the callback. + * The callback should mempcy() the data if it needs to be available outside the callback. + * @param len The length of the data, or 0 in case of an error. */ typedef void (*meshlink_channel_receive_cb_t)(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len); @@ -355,8 +535,11 @@ typedef void (*meshlink_channel_receive_cb_t)(meshlink_handle_t *mesh, meshlink_ * to hand the data over to the application's thread. * The callback should also not block itself and return as quickly as possible. * + * If no accept callback is set, incoming channels are rejected. + * * @param mesh A handle which represents an instance of MeshLink. * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled. */ extern void meshlink_set_channel_accept_cb(meshlink_handle_t *mesh, meshlink_channel_accept_cb_t cb); @@ -370,6 +553,7 @@ extern void meshlink_set_channel_accept_cb(meshlink_handle_t *mesh, meshlink_cha * @param mesh A handle which represents an instance of MeshLink. * @param channel A handle for the channel. * @param cb A pointer to the function which will be called when another node sends data to the local node. + * If a NULL pointer is given, the callback will be disabled and incoming data is ignored. */ extern void meshlink_set_channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_channel_receive_cb_t cb); @@ -377,14 +561,21 @@ extern void meshlink_set_channel_receive_cb(meshlink_handle_t *mesh, meshlink_ch /** This function is called whenever a remote node wants to open a channel to the local node. * The application then has to decide whether to accept or reject this channel. * + * This function returns a pointer to a struct meshlink_channel that will be allocated by MeshLink. + * When the application does no longer need to use this channel, it must call meshlink_close() + * to free its resources. + * * @param mesh A handle which represents an instance of MeshLink. * @param node The node to which this channel is being initiated. * @param port The port number the peer wishes to connect to. * @param cb A pointer to the function which will be called when the remote node sends data to the local node. - * @param data A pointer to a buffer containing data to already queue for sending. - * @param len The length of the data. + * The pointer may be NULL, in which case incoming data is ignored. + * @param data A pointer to a buffer containing data to already queue for sending, or NULL if there is no data to send. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * @param len The length of the data, or 0 if there is no data to send. * * @return A handle for the channel, or NULL in case of an error. + * The handle is valid until meshlink_channel_close() is called. */ extern meshlink_channel_t *meshlink_channel_open(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len); @@ -393,17 +584,23 @@ extern meshlink_channel_t *meshlink_channel_open(meshlink_handle_t *mesh, meshli * It can be used to inform the remote node that the local node has finished sending all data on the channel, * but still allows waiting for incoming data from the remote node. * + * Shutting down the receive direction is also possible, and is equivalent to setting the receive callback to NULL. + * * @param mesh A handle which represents an instance of MeshLink. * @param channel A handle for the channel. - * @param direction Must be one of SHUT_RD, SHUT_WR or SHUT_RDWR. + * @param direction Must be one of SHUT_RD, SHUT_WR or SHUT_RDWR, otherwise this call will not have any affect. */ extern void meshlink_channel_shutdown(meshlink_handle_t *mesh, meshlink_channel_t *channel, int direction); /// Close a reliable stream channel. /** This informs the remote node that the local node has finished sending all data on the channel. * It also causes the local node to stop accepting incoming data from the remote node. + * It will free the struct meshlink_channel and all associated resources. * Afterwards, the channel handle is invalid and must not be used any more. * + * It is allowed to call this function at any time on a valid handle, even inside callback functions. + * If called with a valid handle, this function always succeeds, otherwise the result is undefined. + * * @param mesh A handle which represents an instance of MeshLink. * @param channel A handle for the channel. */ @@ -414,13 +611,65 @@ extern void meshlink_channel_close(meshlink_handle_t *mesh, meshlink_channel_t * * * @param mesh A handle which represents an instance of MeshLink. * @param channel A handle for the channel. - * @param data A pointer to a buffer containing data sent by the source. - * @param len The length of the data. + * @param data A pointer to a buffer containing data sent by the source, or NULL if there is no data to send. + * After meshlink_send() returns, the application is free to overwrite or free this buffer. + * @param len The length of the data, or 0 if there is no data to send. * * @return The amount of data that was queued, which can be less than len, or a negative value in case of an error. */ extern ssize_t meshlink_channel_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len); +/// Hint that a hostname may be found at an address +/** This function indicates to meshlink that the given hostname is likely found + * at the given IP address and port. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param hostname The hostname which can be found at the given address. + * The caller is free to overwrite or free this string + * once meshlink returns. + * @param addr The IP address and port which should be tried for the + * given hostname. The caller is free to overwrite or free + * this memory once meshlink returns. + */ +extern void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const struct sockaddr *addr); + +/// Get a list of edges. +/** This function returns an array with copies of all known bidirectional edges. + * The edges are copied to capture the mesh state at call time, since edges + * mutate frequently. The nodes pointed to within the meshlink_edge_t type + * are not copies; these are the same pointers that one would get from a call + * to meshlink_get_all_nodes(). + * + * @param mesh A handle which represents an instance of MeshLink. + * @param edges A pointer to a previously allocated array of pointers to + * meshlink_edge_t, or NULL in which case MeshLink will + * allocate a new array. The application CANNOT supply an + * array it allocated itself with malloc, but CAN use + * the return value from the previous call to this function + * (which is the preferred way). + * The pointers in the array are valid until meshlink_close() is called. + * ATTENTION: The pointers and values should never be modified + * by the application!!! + * @param nmemb A pointer to a variable holding the number of nodes that + * are stored in the array. In case the @a nodes @a + * 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 + * edges, or NULL in case of an error. + * If the @a edges @a argument was not NULL, then the + * retun value can be either the same value or a different + * value. If the new values is NULL, then the old array + * will have been freed by Meshlink. + * The caller must call free() on each element of this + * array (but not the contents of said elements), + * as well as the array itself when it is finished. + * ATTENTION: The pointers and values should never be modified + * by the application!!! + */ +extern meshlink_edge_t **meshlink_get_all_edges_state(meshlink_handle_t *mesh, meshlink_edge_t **edges, size_t *nmemb); + #ifdef __cplusplus } #endif diff --git a/src/meshlink_internal.h b/src/meshlink_internal.h index 0e035920..517dc1e0 100644 --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@ -24,8 +24,8 @@ #include "event.h" #include "hash.h" -#include "logger.h" #include "meshlink.h" +#include "meshlink_queue.h" #include "sockaddr.h" #include "sptps.h" @@ -33,6 +33,11 @@ #define MAXSOCKETS 8 /* Probably overkill... */ +struct AvahiServer; +struct AvahiSServiceBrowser; +struct AvahiSimplePoll; +struct AvahiSEntryGroup; + typedef struct listen_socket_t { struct io_t tcp; struct io_t udp; @@ -57,8 +62,12 @@ typedef struct outpacketqueue { /// A handle for an instance of MeshLink. struct meshlink_handle { - char *confbase; char *name; + char *appname; + dev_class_t devclass; + void *priv; + + char *confbase; meshlink_receive_cb_t receive_cb; meshlink_node_status_cb_t node_status_cb; @@ -70,7 +79,7 @@ struct meshlink_handle { pthread_t thread; bool threadstarted; pthread_mutex_t outpacketqueue_mutex; - pthread_mutex_t nodes_mutex; + pthread_mutex_t mesh_mutex; event_loop_t loop; listen_socket_t listen_socket[MAXSOCKETS]; int listen_sockets; @@ -85,7 +94,7 @@ struct meshlink_handle { struct list_t *connections; struct list_t *outgoings; - struct list_t *outpacketqueue; + meshlink_queue_t outpacketqueue; struct splay_tree_t *past_request_tree; timeout_t past_request_timeout; @@ -112,7 +121,6 @@ struct meshlink_handle { struct connection_t *everyone; struct ecdsa *invitation_key; - debug_t debug_level; int pinginterval; /* seconds between pings */ int pingtimeout; /* seconds to wait for response */ int maxtimeout; @@ -126,6 +134,14 @@ struct meshlink_handle { char line[4096]; char buffer[4096]; size_t blen; + + pthread_t discovery_thread; + bool discovery_threadstarted; + struct AvahiServer *avahi_server; + struct AvahiSServiceBrowser *avahi_browser; + struct AvahiSimplePoll *avahi_poll; + struct AvahiSEntryGroup *avahi_group; + char* avahi_servicetype; }; /// A handle for a MeshLink node. @@ -148,6 +164,16 @@ typedef struct meshlink_packethdr { } __attribute__ ((__packed__)) meshlink_packethdr_t; extern void meshlink_send_from_queue(event_loop_t* el,meshlink_handle_t *mesh); +extern meshlink_log_level_t global_log_level; +extern meshlink_log_cb_t global_log_cb; + +/// Device class traits +typedef struct { + unsigned int min_connects; + unsigned int max_connects; + int edge_weight; +} dev_class_traits_t; +extern dev_class_traits_t dev_class_traits[]; #endif // MESHLINK_INTERNAL_H diff --git a/src/meshlink_queue.h b/src/meshlink_queue.h new file mode 100644 index 00000000..05b58d9a --- /dev/null +++ b/src/meshlink_queue.h @@ -0,0 +1,69 @@ +/* + queue.h -- Thread-safe queue + Copyright (C) 2014 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef MESHLINK_QUEUE_H +#define MESHLINK_QUEUE_H + +#include +#include +#include +#include + +typedef struct meshlink_queue { + struct meshlink_queue_item *head; + struct meshlink_queue_item *tail; + pthread_mutex_t mutex; +} meshlink_queue_t; + +typedef struct meshlink_queue_item { + void *data; + struct meshlink_queue_item *next; +} meshlink_queue_item_t; + +static inline bool meshlink_queue_push(meshlink_queue_t *queue, void *data) { + meshlink_queue_item_t *item = malloc(sizeof *item); + if(!item) + return false; + item->data = data; + item->next = NULL; + pthread_mutex_lock(&queue->mutex); + if(!queue->tail) + queue->head = queue->tail = item; + else + queue->tail = queue->tail->next = item; + pthread_mutex_unlock(&queue->mutex); + return true; +} + +static inline void *meshlink_queue_pop(meshlink_queue_t *queue) { + meshlink_queue_item_t *item; + void *data; + pthread_mutex_lock(&queue->mutex); + if((item = queue->head)) { + queue->head = item->next; + if(!queue->head) + queue->tail = NULL; + } + pthread_mutex_unlock(&queue->mutex); + data = item ? item->data : NULL; + free(item); + return data; +} + +#endif diff --git a/src/meta.c b/src/meta.c index c402919b..97cbed2a 100644 --- a/src/meta.c +++ b/src/meta.c @@ -33,7 +33,7 @@ bool send_meta_sptps(void *handle, uint8_t type, const void *buffer, size_t leng meshlink_handle_t *mesh = c->mesh; if(!c) { - logger(DEBUG_ALWAYS, LOG_ERR, "send_meta_sptps() called with NULL pointer!"); + logger(mesh, MESHLINK_ERROR, "send_meta_sptps() called with NULL pointer!"); abort(); } @@ -45,11 +45,11 @@ bool send_meta_sptps(void *handle, uint8_t type, const void *buffer, size_t leng bool send_meta(meshlink_handle_t *mesh, connection_t *c, const char *buffer, int length) { if(!c) { - logger(DEBUG_ALWAYS, LOG_ERR, "send_meta() called with NULL pointer!"); + logger(mesh, MESHLINK_ERROR, "send_meta() called with NULL pointer!"); abort(); } - logger(DEBUG_META, LOG_DEBUG, "Sending %d bytes of metadata to %s (%s)", length, + logger(mesh, MESHLINK_DEBUG, "Sending %d bytes of metadata to %s (%s)", length, c->name, c->hostname); if(c->allow_request == ID) { @@ -73,7 +73,7 @@ bool receive_meta_sptps(void *handle, uint8_t type, const void *data, uint16_t l char *request = (char *)data; if(!c) { - logger(DEBUG_ALWAYS, LOG_ERR, "receive_meta_sptps() called with NULL pointer!"); + logger(mesh, MESHLINK_ERROR, "receive_meta_sptps() called with NULL pointer!"); abort(); } @@ -124,7 +124,7 @@ bool receive_meta(meshlink_handle_t *mesh, connection_t *c) { buffer_compact(&c->inbuf, MAXBUFSIZE); if(sizeof inbuf <= c->inbuf.len) { - logger(DEBUG_ALWAYS, LOG_ERR, "Input buffer full for %s (%s)", c->name, c->hostname); + logger(mesh, MESHLINK_ERROR, "Input buffer full for %s (%s)", c->name, c->hostname); return false; } @@ -132,12 +132,12 @@ bool receive_meta(meshlink_handle_t *mesh, connection_t *c) { if(inlen <= 0) { if(!inlen || !errno) { - logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", + logger(mesh, MESHLINK_INFO, "Connection closed by %s (%s)", c->name, c->hostname); } else if(sockwouldblock(sockerrno)) return true; else - logger(DEBUG_ALWAYS, LOG_ERR, "Metadata socket read error for %s (%s): %s", + logger(mesh, MESHLINK_ERROR, "Metadata socket read error for %s (%s): %s", c->name, c->hostname, sockstrerror(sockerrno)); return false; } diff --git a/src/net.c b/src/net.c index 5f50531b..8400b936 100644 --- a/src/net.c +++ b/src/net.c @@ -31,6 +31,12 @@ #include "protocol.h" #include "xalloc.h" +#include + +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,173 @@ static void timeout_handler(event_loop_t *loop, void *data) { timeout_set(&mesh->loop, data, &(struct timeval){mesh->pingtimeout, rand() % 100000}); } +// devclass asc, last_successfull_connection desc +static int node_compare_devclass_asc_lsc_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_successfull_connection == nb->last_successfull_connection) + return 0; + + if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) + return -1; + + if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) + return 1; + + if(na < nb) + return -1; + + if(na > nb) + return 1; + + return 0; +} + +// last_successfull_connection desc +static int node_compare_lsc_desc(const void *a, const void *b) +{ + const node_t *na = a, *nb = b; + + if(na->last_successfull_connection == nb->last_successfull_connection) + return 0; + + if(na->last_successfull_connection == 0 || na->last_successfull_connection > nb->last_successfull_connection) + return -1; + + if(nb->last_successfull_connection == 0 || na->last_successfull_connection < nb->last_successfull_connection) + return 1; + + if(na < nb) + return -1; + + if(na > nb) + 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; } + + if(na < nb) + return -1; + + if(na > nb) + 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 +307,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,99 +321,262 @@ 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); + + logger(mesh, MESHLINK_INFO, "* devclass = %d", mesh->devclass); + logger(mesh, MESHLINK_INFO, "* nodes = %d", mesh->nodes->count); + logger(mesh, MESHLINK_INFO, "* retry_timeout = %d", retry_timeout); + + + // connect disconnect nodes - 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++; + node_t* connect_to = NULL; + node_t* disconnect_from = NULL; + + + // get cur_connects + + int cur_connects = 0; + + for list_each(connection_t, c, mesh->connections) + { + if(!c->status.remove_unused) + { + cur_connects += 1; + } } - 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; + logger(mesh, MESHLINK_INFO, "* cur_connects = %d", cur_connects); + logger(mesh, MESHLINK_INFO, "* outgoings = %d", mesh->outgoings->count); - if(n->connection) - break; + // get min_connects and max_connects + + assert(mesh->devclass >= 0 && mesh->devclass <= _DEV_CLASS_MAX); - bool found = false; + int min_connects = dev_class_traits[mesh->devclass].min_connects; + int max_connects = dev_class_traits[mesh->devclass].max_connects; - for list_each(outgoing_t, outgoing, mesh->outgoings) { - if(!strcmp(outgoing->name, n->name)) { - found = true; + 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_lsc_desc, NULL); + + for splay_each(node_t, n, mesh->nodes) + { + logger(mesh, MESHLINK_INFO, "* n->devclass = %d", n->devclass); + if(n != mesh->self && 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; + } + else + { logger(mesh, MESHLINK_INFO, "* could not find node for initial connect"); } + + splay_free_tree(nodes); + } + + + // find better nodes to connect to + + if(!connect_to && 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( connects < min_connects ) + { + splay_tree_t *nodes = splay_alloc_tree(node_compare_lsc_desc, NULL); + + for splay_each(node_t, n, mesh->nodes) + { + if(n != mesh->self && n->devclass == 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 better node"); + connect_to = (node_t*)nodes->head->data; + + 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; } } - } else 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(!connect_to) + { logger(mesh, MESHLINK_INFO, "* could not find better nodes"); } + } + + + // heal partitions + + if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) + { + splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL); + + for splay_each(node_t, n, mesh->nodes) + { + if(n != mesh->self && 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; + } + else + { logger(mesh, MESHLINK_INFO, "* could not find nodes for partition healing"); } + + splay_free_tree(nodes); + } + - if(!c->outgoing || !c->node || c->node->edge_tree->count < 2) + // perform connect + + if(connect_to && !connect_to->connection) + { + connect_to->last_connect_try = time(NULL); + + /* 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); } + else + { logger(mesh, MESHLINK_INFO, "* skip autoconnect since it is an outgoing connection already"); } } - if(nc >= autoconnect) { - /* If we have enough active connections, - remove any pending outgoing connections. - */ - for list_each(outgoing_t, o, mesh->outgoings) { - 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); - 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; } } + + if(!disconnect_from) + { logger(mesh, MESHLINK_INFO, "* no suboptimal outgoing connections"); } + } + + + // 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; + } + else + { logger(mesh, MESHLINK_INFO, "* no node we want to disconnect, even though we have too many connections"); } + + 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) { @@ -267,14 +609,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; } diff --git a/src/net.h b/src/net.h index 6014a3d6..d2a60bcf 100644 --- a/src/net.h +++ b/src/net.h @@ -76,7 +76,6 @@ extern unsigned replaywin; extern int keylifetime; extern int max_connection_burst; extern bool do_prune; -extern int autoconnect; /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */ #include "connection.h" diff --git a/src/net_packet.c b/src/net_packet.c index 8367a9c5..ca43d62c 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -68,7 +68,7 @@ static void send_mtu_probe_handler(event_loop_t *loop, void *data) { n->mtuprobes++; if(!n->status.reachable || !n->status.validkey) { - logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname); + logger(mesh, MESHLINK_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname); n->mtuprobes = 0; return; } @@ -80,7 +80,7 @@ static void send_mtu_probe_handler(event_loop_t *loop, void *data) { goto end; } - logger(DEBUG_TRAFFIC, LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname); + logger(mesh, MESHLINK_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname); n->status.udp_confirmed = false; n->mtuprobes = 1; n->minmtu = 0; @@ -88,7 +88,7 @@ static void send_mtu_probe_handler(event_loop_t *loop, void *data) { } if(n->mtuprobes >= 10 && n->mtuprobes < 32 && !n->minmtu) { - logger(DEBUG_TRAFFIC, LOG_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname); + logger(mesh, MESHLINK_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname); n->mtuprobes = 31; } @@ -98,7 +98,7 @@ static void send_mtu_probe_handler(event_loop_t *loop, void *data) { else n->maxmtu = n->minmtu; n->mtu = n->minmtu; - logger(DEBUG_TRAFFIC, LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes); + logger(mesh, MESHLINK_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes); n->mtuprobes = 31; } @@ -132,7 +132,7 @@ static void send_mtu_probe_handler(event_loop_t *loop, void *data) { packet.len = len; n->status.broadcast = i >= 4 && n->mtuprobes <= 10 && n->prevedge; - logger(DEBUG_TRAFFIC, LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname); + logger(mesh, MESHLINK_DEBUG, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname); send_udppacket(mesh, n, &packet); } @@ -163,7 +163,7 @@ void send_mtu_probe(meshlink_handle_t *mesh, node_t *n) { } static void mtu_probe_h(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet, uint16_t len) { - logger(DEBUG_TRAFFIC, LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname); + logger(mesh, MESHLINK_DEBUG, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname); if(!packet->data[0]) { /* It's a probe request, send back a reply */ @@ -188,7 +188,7 @@ static void mtu_probe_h(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet if(n->mtuprobes > 30) { if (len == n->maxmtu + 8) { - logger(DEBUG_TRAFFIC, LOG_INFO, "Increase in PMTU to %s (%s) detected, restarting PMTU discovery", n->name, n->hostname); + logger(mesh, MESHLINK_INFO, "Increase in PMTU to %s (%s) detected, restarting PMTU discovery", n->name, n->hostname); n->maxmtu = MTU; n->mtuprobes = 10; return; @@ -224,7 +224,7 @@ static void mtu_probe_h(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet n->probe_time = now; } else if(n->probe_counter == 3) { n->bandwidth = 2.0 * len / (diff.tv_sec + diff.tv_usec * 1e-6); - logger(DEBUG_TRAFFIC, LOG_DEBUG, "%s (%s) RTT %.2f ms, burst bandwidth %.3f Mbit/s, rx packet loss %.2f %%", n->name, n->hostname, n->rtt * 1e3, n->bandwidth * 8e-6, n->packetloss * 1e2); + logger(mesh, MESHLINK_DEBUG, "%s (%s) RTT %.2f ms, burst bandwidth %.3f Mbit/s, rx packet loss %.2f %%", n->name, n->hostname, n->rtt * 1e3, n->bandwidth * 8e-6, n->packetloss * 1e2); } } } @@ -273,11 +273,11 @@ static uint16_t uncompress_packet(uint8_t *dest, const uint8_t *source, uint16_t /* VPN packet I/O */ static void receive_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet) { - logger(DEBUG_TRAFFIC, LOG_DEBUG, "Received packet of %d bytes from %s (%s)", + logger(mesh, MESHLINK_DEBUG, "Received packet of %d bytes from %s (%s)", packet->len, n->name, n->hostname); if (n->status.blacklisted) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Dropping packet from blacklisted node %s", n->name); + logger(mesh, MESHLINK_WARNING, "Dropping packet from blacklisted node %s", n->name); } else { n->in_packets++; n->in_bytes += packet->len; @@ -293,10 +293,10 @@ static bool try_mac(meshlink_handle_t *mesh, node_t *n, const vpn_packet_t *inpk static void receive_udppacket(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *inpkt) { if(!n->sptps.state) { if(!n->status.waitingforkey) { - logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but we haven't exchanged keys yet", n->name, n->hostname); + logger(mesh, MESHLINK_DEBUG, "Got packet from %s (%s) but we haven't exchanged keys yet", n->name, n->hostname); send_req_key(mesh, n); } else { - logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname); + logger(mesh, MESHLINK_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname); } return; } @@ -318,11 +318,11 @@ void receive_tcppacket(meshlink_handle_t *mesh, connection_t *c, const char *buf static void send_sptps_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *origpkt) { if(!n->status.validkey) { - logger(DEBUG_TRAFFIC, LOG_INFO, "No valid key known yet for %s (%s)", n->name, n->hostname); + logger(mesh, MESHLINK_INFO, "No valid key known yet for %s (%s)", n->name, n->hostname); if(!n->status.waitingforkey) send_req_key(mesh, n); else if(n->last_req_key + 10 < mesh->loop.now.tv_sec) { - logger(DEBUG_ALWAYS, LOG_DEBUG, "No key from %s after 10 seconds, restarting SPTPS", n->name); + logger(mesh, MESHLINK_DEBUG, "No key from %s after 10 seconds, restarting SPTPS", n->name); sptps_stop(&n->sptps); n->status.waitingforkey = false; send_req_key(mesh, n); @@ -343,7 +343,7 @@ static void send_sptps_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t * if(n->outcompression) { int len = compress_packet(outpkt.data, origpkt->data, origpkt->len, n->outcompression); if(len < 0) { - logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)", n->name, n->hostname); + logger(mesh, MESHLINK_ERROR, "Error while compressing packet to %s (%s)", n->name, n->hostname); } else if(len < origpkt->len) { outpkt.len = len; origpkt = &outpkt; @@ -445,7 +445,7 @@ static void choose_broadcast_address(meshlink_handle_t *mesh, const node_t *n, c static void send_udppacket(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *origpkt) { if(!n->status.reachable) { - logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname); + logger(mesh, MESHLINK_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname); return; } @@ -488,7 +488,7 @@ bool send_sptps_data(void *handle, uint8_t type, const void *data, size_t len) { if(to->mtu >= len) to->mtu = len - 1; } else { - logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", to->name, to->hostname, sockstrerror(sockerrno)); + logger(mesh, MESHLINK_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", to->name, to->hostname, sockstrerror(sockerrno)); return false; } } @@ -504,13 +504,13 @@ bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t if(!from->status.validkey) { from->status.validkey = true; from->status.waitingforkey = false; - logger(DEBUG_META, LOG_INFO, "SPTPS key exchange with %s (%s) succesful", from->name, from->hostname); + logger(mesh, MESHLINK_INFO, "SPTPS key exchange with %s (%s) succesful", from->name, from->hostname); } return true; } if(len > MTU) { - logger(DEBUG_ALWAYS, LOG_ERR, "Packet from %s (%s) larger than maximum supported size (%d > %d)", from->name, from->hostname, len, MTU); + logger(mesh, MESHLINK_ERROR, "Packet from %s (%s) larger than maximum supported size (%d > %d)", from->name, from->hostname, len, MTU); return false; } @@ -527,7 +527,7 @@ bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t } if(type & ~(PKT_COMPRESSED)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Unexpected SPTPS record type %d len %d from %s (%s)", type, len, from->name, from->hostname); + logger(mesh, MESHLINK_ERROR, "Unexpected SPTPS record type %d len %d from %s (%s)", type, len, from->name, from->hostname); return false; } @@ -560,11 +560,11 @@ void send_packet(meshlink_handle_t *mesh, node_t *n, vpn_packet_t *packet) { return; } - logger(DEBUG_TRAFFIC, LOG_ERR, "Sending packet of %d bytes to %s (%s)", + logger(mesh, MESHLINK_DEBUG, "Sending packet of %d bytes to %s (%s)", packet->len, n->name, n->hostname); if(!n->status.reachable) { - logger(DEBUG_TRAFFIC, LOG_INFO, "Node %s (%s) is not reachable", + logger(mesh, MESHLINK_WARNING, "Node %s (%s) is not reachable", n->name, n->hostname); return; } @@ -583,7 +583,7 @@ void broadcast_packet(meshlink_handle_t *mesh, const node_t *from, vpn_packet_t if(from != mesh->self) send_packet(mesh, mesh->self, packet); - logger(DEBUG_TRAFFIC, LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)", + logger(mesh, MESHLINK_INFO, "Broadcasting packet of %d bytes from %s (%s)", packet->len, from->name, from->hostname); for list_each(connection_t, c, mesh->connections) @@ -634,7 +634,7 @@ void handle_incoming_vpn_data(event_loop_t *loop, void *data, int flags) { if(len <= 0 || len > MAXSIZE) { if(!sockwouldblock(sockerrno)) - logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Receiving packet failed: %s", sockstrerror(sockerrno)); return; } @@ -648,9 +648,9 @@ void handle_incoming_vpn_data(event_loop_t *loop, void *data, int flags) { n = try_harder(mesh, &from, &pkt); if(n) update_node_udp(mesh, n, &from); - else if(mesh->debug_level >= DEBUG_PROTOCOL) { + else if(mesh->log_level >= MESHLINK_WARNING) { hostname = sockaddr2hostname(&from); - logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from unknown source %s", hostname); + logger(mesh, MESHLINK_WARNING, "Received UDP packet from unknown source %s", hostname); free(hostname); return; } @@ -659,7 +659,7 @@ void handle_incoming_vpn_data(event_loop_t *loop, void *data, int flags) { } if (n->status.blacklisted) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Dropping packet from blacklisted node %s", n->name); + logger(mesh, MESHLINK_WARNING, "Dropping packet from blacklisted node %s", n->name); return; } n->sock = ls - mesh->listen_socket; diff --git a/src/net_setup.c b/src/net_setup.c index 8b0fd8fd..1ba98077 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -32,8 +32,6 @@ #include "utils.h" #include "xalloc.h" -int autoconnect = 3; - bool node_read_ecdsa_public_key(meshlink_handle_t *mesh, node_t *n) { if(ecdsa_active(n->ecdsa)) return true; @@ -88,7 +86,7 @@ bool read_ecdsa_private_key(meshlink_handle_t *mesh) { fp = fopen(filename, "r"); if(!fp) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error reading ECDSA private key file: %s", strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Error reading ECDSA private key file: %s", strerror(errno)); return false; } @@ -96,7 +94,7 @@ bool read_ecdsa_private_key(meshlink_handle_t *mesh) { fclose(fp); if(!mesh->self->connection->ecdsa) - logger(DEBUG_ALWAYS, LOG_ERR, "Reading ECDSA private key file failed: %s", strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Reading ECDSA private key file failed: %s", strerror(errno)); return mesh->self->connection->ecdsa; } @@ -118,12 +116,69 @@ static bool read_invitation_key(meshlink_handle_t *mesh) { mesh->invitation_key = ecdsa_read_pem_private_key(fp); fclose(fp); if(!mesh->invitation_key) - logger(DEBUG_ALWAYS, LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", filename, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Reading ECDSA private key file `%s' failed: %s", filename, strerror(errno)); } return mesh->invitation_key; } +bool node_read_devclass(meshlink_handle_t *mesh, node_t *n) { + + splay_tree_t *config_tree; + char *p; + + init_configuration(&config_tree); + if(!read_host_config(mesh, config_tree, n->name)) + goto exit; + + if(get_config_string(lookup_config(config_tree, "DeviceClass"), &p)) + { + n->devclass = atoi(p); + free(p); + } + + if(n->devclass < 0 || n->devclass > _DEV_CLASS_MAX) + { n->devclass = _DEV_CLASS_MAX; } + +exit: + exit_configuration(&config_tree); + return n->devclass != 0; +} + +bool node_write_devclass(meshlink_handle_t *mesh, node_t *n) { + + if(n->devclass < 0 || n->devclass > _DEV_CLASS_MAX) + return false; + + bool result = false; + + splay_tree_t *config_tree; + init_configuration(&config_tree); + + // ignore read errors; in case the file does not exist we will create it + read_host_config(mesh, config_tree, n->name); + + config_t* cnf = lookup_config(config_tree, "DeviceClass"); + + if(!cnf) + { + cnf = new_config(); + cnf->variable = xstrdup("DeviceClass"); + config_add(config_tree, cnf); + } + + set_config_int(cnf, n->devclass); + + if(!write_host_config(mesh, config_tree, n->name)) + goto fail; + + result = true; + +fail: + exit_configuration(&config_tree); + return result; +} + void load_all_nodes(meshlink_handle_t *mesh) { DIR *dir; struct dirent *ent; @@ -132,7 +187,7 @@ void load_all_nodes(meshlink_handle_t *mesh) { snprintf(dname,PATH_MAX, "%s" SLASH "hosts", mesh->confbase); dir = opendir(dname); if(!dir) { - logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", dname, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno)); return; } @@ -146,6 +201,7 @@ void load_all_nodes(meshlink_handle_t *mesh) { n = new_node(); n->name = xstrdup(ent->d_name); + node_read_devclass(mesh, n); node_add(mesh, n); } @@ -162,7 +218,7 @@ char *get_name(meshlink_handle_t *mesh) { return NULL; if(!check_id(name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for mesh->self!"); + logger(mesh, MESHLINK_ERROR, "Invalid name for mesh->self!"); free(name); return NULL; } @@ -174,7 +230,6 @@ bool setup_myself_reloadable(meshlink_handle_t *mesh) { mesh->localdiscovery = true; keylifetime = 3600; // TODO: check if this can be removed as well mesh->maxtimeout = 900; - autoconnect = 3; mesh->self->options |= OPTION_PMTU_DISCOVERY; read_invitation_key(mesh); @@ -209,7 +264,7 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind free(address); if(err || !ai) { - logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "getaddrinfo", err == EAI_SYSTEM ? strerror(err) : gai_strerror(err)); + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "getaddrinfo", err == EAI_SYSTEM ? strerror(err) : gai_strerror(err)); return false; } @@ -227,7 +282,7 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind continue; if(mesh->listen_sockets >= MAXSOCKETS) { - logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets"); + logger(mesh, MESHLINK_ERROR, "Too many listening sockets"); return false; } @@ -246,9 +301,9 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind io_add(&mesh->loop, &mesh->listen_socket[mesh->listen_sockets].tcp, handle_new_meta_connection, &mesh->listen_socket[mesh->listen_sockets], tcp_fd, IO_READ); io_add(&mesh->loop, &mesh->listen_socket[mesh->listen_sockets].udp, handle_incoming_vpn_data, &mesh->listen_socket[mesh->listen_sockets], udp_fd, IO_READ); - if(mesh->debug_level >= DEBUG_CONNECTIONS) { + if(mesh->log_level >= MESHLINK_INFO) { char *hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr); - logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname); + logger(mesh, MESHLINK_INFO, "Listening on %s", hostname); free(hostname); } @@ -269,18 +324,21 @@ bool setup_myself(meshlink_handle_t *mesh) { char *address = NULL; if(!(name = get_name(mesh))) { - logger(DEBUG_ALWAYS, LOG_ERR, "Name for MeshLink instance required!"); + logger(mesh, MESHLINK_ERROR, "Name for MeshLink instance required!"); return false; } mesh->self = new_node(); mesh->self->connection = new_connection(); mesh->self->name = name; + mesh->self->devclass = mesh->devclass; mesh->self->connection->name = xstrdup(name); read_host_config(mesh, mesh->config, name); - if(!get_config_string(lookup_config(mesh->config, "Port"), &mesh->myport)) - mesh->myport = xstrdup("655"); + if(!get_config_string(lookup_config(mesh->config, "Port"), &mesh->myport)) { + logger(mesh, MESHLINK_ERROR, "Port for MeshLink instance required!"); + return false; + } mesh->self->connection->options = 0; mesh->self->connection->protocol_major = PROT_MAJOR; @@ -320,12 +378,13 @@ bool setup_myself(meshlink_handle_t *mesh) { mesh->self->via = mesh->self; mesh->self->status.reachable = true; mesh->self->last_state_change = mesh->loop.now.tv_sec; + + node_write_devclass(mesh, mesh->self); node_add(mesh, mesh->self); graph(mesh); - if(autoconnect) - load_all_nodes(mesh); + load_all_nodes(mesh); /* Open sockets */ @@ -335,15 +394,10 @@ bool setup_myself(meshlink_handle_t *mesh) { return false; if(!mesh->listen_sockets) { - logger(DEBUG_ALWAYS, LOG_ERR, "Unable to create any listening socket!"); + logger(mesh, MESHLINK_ERROR, "Unable to create any listening socket!"); return false; } - // TODO: require Port to be set? Or use "0" and use getsockname()? - - if(!mesh->myport) - mesh->myport = xstrdup("655"); - xasprintf(&mesh->self->hostname, "MYSELF port %s", mesh->myport); mesh->self->connection->hostname = xstrdup(mesh->self->hostname); @@ -377,11 +431,13 @@ bool setup_network(meshlink_handle_t *mesh) { close all open network connections */ void close_network_connections(meshlink_handle_t *mesh) { - for(list_node_t *node = mesh->connections->head, *next; node; node = next) { - next = node->next; - connection_t *c = node->data; - c->outgoing = NULL; - terminate_connection(mesh, c, false); + if(mesh->connections) { + for(list_node_t *node = mesh->connections->head, *next; node; node = next) { + next = node->next; + connection_t *c = node->data; + c->outgoing = NULL; + terminate_connection(mesh, c, false); + } } if(mesh->outgoings) diff --git a/src/net_socket.c b/src/net_socket.c index 85f51648..d5786e5b 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -47,13 +47,13 @@ static void configure_tcp(connection_t *c) { int flags = fcntl(c->socket, F_GETFL); if(fcntl(c->socket, F_SETFL, flags | O_NONBLOCK) < 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "fcntl for %s: %s", c->hostname, strerror(errno)); + logger(c->mesh, MESHLINK_ERROR, "fcntl for %s: %s", c->hostname, strerror(errno)); } #elif defined(WIN32) unsigned long arg = 1; if(ioctlsocket(c->socket, FIONBIO, &arg) != 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "ioctlsocket for %s: %s", c->hostname, sockstrerror(sockerrno)); + logger(c->mesh, MESHLINK_ERROR, "ioctlsocket for %s: %s", c->hostname, sockstrerror(sockerrno)); } #endif @@ -89,7 +89,7 @@ static bool bind_to_address(meshlink_handle_t *mesh, connection_t *c) { sa.in6.sin6_port = 0; if(bind(c->socket, &sa.sa, SALEN(sa.sa))) { - logger(DEBUG_CONNECTIONS, LOG_WARNING, "Can't bind outgoing socket: %s", strerror(errno)); + logger(mesh, MESHLINK_WARNING, "Can't bind outgoing socket: %s", strerror(errno)); return false; } @@ -104,7 +104,7 @@ int setup_listen_socket(const sockaddr_t *sa) { nfd = socket(sa->sa.sa_family, SOCK_STREAM, IPPROTO_TCP); if(nfd < 0) { - logger(DEBUG_STATUS, LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); + logger(NULL, MESHLINK_ERROR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); return -1; } @@ -125,14 +125,14 @@ int setup_listen_socket(const sockaddr_t *sa) { if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); addrstr = sockaddr2hostname(sa); - logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); + logger(NULL, MESHLINK_ERROR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); free(addrstr); return -1; } if(listen(nfd, 3)) { closesocket(nfd); - logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); + logger(NULL, MESHLINK_ERROR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); return -1; } @@ -147,7 +147,7 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { nfd = socket(sa->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); if(nfd < 0) { - logger(DEBUG_ALWAYS, LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); return -1; } @@ -161,7 +161,7 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0) { closesocket(nfd); - logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "fcntl", strerror(errno)); return -1; } @@ -171,7 +171,7 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { unsigned long arg = 1; if(ioctlsocket(nfd, FIONBIO, &arg) != 0) { closesocket(nfd); - logger(DEBUG_ALWAYS, LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); return -1; } } @@ -221,7 +221,7 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); addrstr = sockaddr2hostname(sa); - logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); free(addrstr); return -1; } @@ -243,11 +243,11 @@ void retry_outgoing(meshlink_handle_t *mesh, outgoing_t *outgoing) { timeout_add(&mesh->loop, &outgoing->ev, retry_outgoing_handler, outgoing, &(struct timeval){outgoing->timeout, rand() % 100000}); - logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Trying to re-establish outgoing connection in %d seconds", outgoing->timeout); + logger(mesh, MESHLINK_INFO, "Trying to re-establish outgoing connection in %d seconds", outgoing->timeout); } void finish_connecting(meshlink_handle_t *mesh, connection_t *c) { - logger(DEBUG_CONNECTIONS, LOG_INFO, "Connected to %s (%s)", c->name, c->hostname); + logger(mesh, MESHLINK_INFO, "Connected to %s (%s)", c->name, c->hostname); c->last_ping_time = mesh->loop.now.tv_sec; c->status.connecting = false; @@ -260,14 +260,14 @@ static void do_outgoing_pipe(meshlink_handle_t *mesh, connection_t *c, char *com int fd[2]; if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Could not create socketpair: %s", strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Could not create socketpair: %s", strerror(errno)); return; } if(fork()) { c->socket = fd[0]; close(fd[1]); - logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Using proxy %s", command); + logger(mesh, MESHLINK_DEBUG, "Using proxy %s", command); return; } @@ -291,12 +291,12 @@ static void do_outgoing_pipe(meshlink_handle_t *mesh, connection_t *c, char *com int result = system(command); if(result < 0) - logger(DEBUG_ALWAYS, LOG_ERR, "Could not execute %s: %s", command, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Could not execute %s: %s", command, strerror(errno)); else if(result) - logger(DEBUG_ALWAYS, LOG_ERR, "%s exited with non-zero status %d", command, result); + logger(mesh, MESHLINK_ERROR, "%s exited with non-zero status %d", command, result); exit(result); #else - logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type exec not supported on this platform!"); + logger(mesh, MESHLINK_ERROR, "Proxy type exec not supported on this platform!"); return; #endif } @@ -305,15 +305,15 @@ static void handle_meta_write(meshlink_handle_t *mesh, connection_t *c) { if(c->outbuf.len <= c->outbuf.offset) return; - ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0); + ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, MSG_NOSIGNAL); if(outlen <= 0) { if(!errno || errno == EPIPE) { - logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", c->name, c->hostname); + logger(mesh, MESHLINK_INFO, "Connection closed by %s (%s)", c->name, c->hostname); } else if(sockwouldblock(sockerrno)) { - logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Sending %d bytes to %s (%s) would block", c->outbuf.len - c->outbuf.offset, c->name, c->hostname); + logger(mesh, MESHLINK_DEBUG, "Sending %d bytes to %s (%s) would block", c->outbuf.len - c->outbuf.offset, c->name, c->hostname); return; } else { - logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not send %d bytes of data to %s (%s): %s", c->outbuf.len - c->outbuf.offset, c->name, c->hostname, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Could not send %d bytes of data to %s (%s): %s", c->outbuf.len - c->outbuf.offset, c->name, c->hostname, strerror(errno)); } terminate_connection(mesh, c, c->status.active); @@ -339,7 +339,7 @@ static void handle_meta_io(event_loop_t *loop, void *data, int flags) { if(!result) finish_connecting(mesh, c); else { - logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Error while connecting to %s (%s): %s", c->name, c->hostname, sockstrerror(result)); + logger(mesh, MESHLINK_DEBUG, "Error while connecting to %s (%s): %s", c->name, c->hostname, sockstrerror(result)); terminate_connection(mesh, c, false); return; } @@ -359,7 +359,7 @@ bool do_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { begin: if(!outgoing->ai) { if(!outgoing->cfg) { - logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s", outgoing->name); + logger(mesh, MESHLINK_ERROR, "Could not set up a meta connection to %s", outgoing->name); retry_outgoing(mesh, outgoing); return false; } @@ -372,8 +372,11 @@ begin: *space = 0; } else { // TODO: Only allow Address statements? - if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) - port = xstrdup("655"); + if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) { + logger(mesh, MESHLINK_ERROR, "No Port known for %s", outgoing->name); + retry_outgoing(mesh, outgoing); + return false; + } } outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); @@ -399,7 +402,7 @@ begin: c->hostname = sockaddr2hostname(&c->address); - logger(DEBUG_CONNECTIONS, LOG_INFO, "Trying to connect to %s (%s)", outgoing->name, c->hostname); + logger(mesh, MESHLINK_INFO, "Trying to connect to %s (%s)", outgoing->name, c->hostname); if(!mesh->proxytype) { c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); @@ -412,13 +415,13 @@ begin: free_connection(c); goto begin; } - logger(DEBUG_CONNECTIONS, LOG_INFO, "Using proxy at %s port %s", mesh->proxyhost, mesh->proxyport); + logger(mesh, MESHLINK_INFO, "Using proxy at %s port %s", mesh->proxyhost, mesh->proxyport); c->socket = socket(proxyai->ai_family, SOCK_STREAM, IPPROTO_TCP); configure_tcp(c); } if(c->socket == -1) { - logger(DEBUG_CONNECTIONS, LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); free_connection(c); goto begin; } @@ -449,7 +452,7 @@ begin: } if(result == -1 && !sockinprogress(sockerrno)) { - logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not connect to %s (%s): %s", outgoing->name, c->hostname, sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Could not connect to %s (%s): %s", outgoing->name, c->hostname, sockstrerror(sockerrno)); free_connection(c); goto begin; @@ -509,7 +512,7 @@ void setup_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { node_t *n = lookup_node(mesh, outgoing->name); if(n && n->connection) { - logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", outgoing->name); + logger(mesh, MESHLINK_INFO, "Already connected to %s", outgoing->name); n->connection->outgoing = outgoing; return; @@ -526,7 +529,7 @@ void setup_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { if(n) outgoing->aip = outgoing->ai = get_known_addresses(n); if(!outgoing->ai) { - logger(DEBUG_ALWAYS, LOG_ERR, "No address known for %s", outgoing->name); + logger(mesh, MESHLINK_ERROR, "No address known for %s", outgoing->name); return; } } @@ -554,7 +557,7 @@ void handle_new_meta_connection(event_loop_t *loop, void *data, int flags) { return; } - logger(DEBUG_ALWAYS, LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); + logger(mesh, MESHLINK_ERROR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); return; } @@ -620,7 +623,7 @@ void handle_new_meta_connection(event_loop_t *loop, void *data, int flags) { c->socket = fd; c->last_ping_time = mesh->loop.now.tv_sec; - logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection from %s", c->hostname); + logger(mesh, MESHLINK_INFO, "Connection from %s", c->hostname); io_add(&mesh->loop, &c->io, handle_meta_io, c, c->socket, IO_READ); @@ -667,7 +670,7 @@ void try_outgoing_connections(meshlink_handle_t *mesh) { get_config_string(cfg, &name); if(!check_id(name)) { - logger(DEBUG_ALWAYS, LOG_ERR, + logger(mesh, MESHLINK_ERROR, "Invalid name for outgoing connection in %s line %d", cfg->file, cfg->line); free(name); @@ -698,7 +701,7 @@ void try_outgoing_connections(meshlink_handle_t *mesh) { for list_each(connection_t, c, mesh->connections) { if(c->outgoing && c->outgoing->timeout == -1) { c->outgoing = NULL; - logger(DEBUG_CONNECTIONS, LOG_INFO, "No more outgoing connection to %s", c->name); + logger(mesh, MESHLINK_INFO, "No more outgoing connection to %s", c->name); terminate_connection(mesh, c, c->status.active); } } diff --git a/src/netutl.c b/src/netutl.c index aefde072..11987afd 100644 --- a/src/netutl.c +++ b/src/netutl.c @@ -41,7 +41,7 @@ struct addrinfo *str2addrinfo(const char *address, const char *service, int sock err = getaddrinfo(address, service, &hint, &ai); if(err) { - logger(DEBUG_ALWAYS, LOG_WARNING, "Error looking up %s port %s: %s", address, service, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + logger(NULL, MESHLINK_WARNING, "Error looking up %s port %s: %s", address, service, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); return NULL; } @@ -60,7 +60,7 @@ sockaddr_t str2sockaddr(const char *address, const char *port) { err = getaddrinfo(address, port, &hint, &ai); if(err || !ai) { - logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Unknown type address %s port %s", address, port); + logger(NULL, MESHLINK_DEBUG, "Unknown type address %s port %s", address, port); result.sa.sa_family = AF_UNKNOWN; result.unknown.address = xstrdup(address); result.unknown.port = xstrdup(port); @@ -90,7 +90,7 @@ void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV); if(err) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error while translating addresses: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + logger(NULL, MESHLINK_ERROR, "Error while translating addresses: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); abort(); } @@ -119,7 +119,7 @@ char *sockaddr2hostname(const sockaddr_t *sa) { err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, hostnames ? 0 : (NI_NUMERICHOST | NI_NUMERICSERV)); if(err) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error while looking up hostname: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + logger(NULL, MESHLINK_ERROR, "Error while looking up hostname: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); } xasprintf(&str, "%s port %s", address, port); @@ -149,7 +149,7 @@ int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b) { return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)); default: - logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", + logger(NULL, MESHLINK_ERROR, "sockaddrcmp() was called with unknown address family %d, exitting!", a->sa.sa_family); abort(); } @@ -192,7 +192,7 @@ int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) { return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof a->in6.sin6_port); default: - logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", + logger(NULL, MESHLINK_ERROR, "sockaddrcmp() was called with unknown address family %d, exitting!", a->sa.sa_family); abort(); } diff --git a/src/node.c b/src/node.c index 7fc476f4..c6213a17 100644 --- a/src/node.c +++ b/src/node.c @@ -34,17 +34,17 @@ static int node_compare(const node_t *a, const node_t *b) { } void init_nodes(meshlink_handle_t *mesh) { - pthread_mutex_lock(&(mesh->nodes_mutex)); mesh->nodes = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node); mesh->node_udp_cache = hash_alloc(0x100, sizeof(sockaddr_t)); - pthread_mutex_unlock(&(mesh->nodes_mutex)); } void exit_nodes(meshlink_handle_t *mesh) { - pthread_mutex_lock(&(mesh->nodes_mutex)); - hash_free(mesh->node_udp_cache); - splay_delete_tree(mesh->nodes); - pthread_mutex_unlock(&(mesh->nodes_mutex)); + if(mesh->node_udp_cache) + hash_free(mesh->node_udp_cache); + if(mesh->nodes) + splay_delete_tree(mesh->nodes); + mesh->node_udp_cache = NULL; + mesh->nodes = NULL; } node_t *new_node(void) { @@ -54,6 +54,7 @@ node_t *new_node(void) { n->edge_tree = new_edge_tree(); n->mtu = MTU; n->maxmtu = MTU; + n->devclass = _DEV_CLASS_MAX; return n; } @@ -83,32 +84,24 @@ void free_node(node_t *n) { } void node_add(meshlink_handle_t *mesh, node_t *n) { - pthread_mutex_lock(&(mesh->nodes_mutex)); n->mesh = mesh; splay_insert(mesh->nodes, n); - pthread_mutex_unlock(&(mesh->nodes_mutex)); } void node_del(meshlink_handle_t *mesh, node_t *n) { - pthread_mutex_lock(&(mesh->nodes_mutex)); timeout_del(&mesh->loop, &n->mtutimeout); for splay_each(edge_t, e, n->edge_tree) edge_del(mesh, e); splay_delete(mesh->nodes, n); - pthread_mutex_unlock(&(mesh->nodes_mutex)); } -node_t *lookup_node(meshlink_handle_t *mesh, char *name) { - node_t n = {NULL}; - node_t* result; +node_t *lookup_node(meshlink_handle_t *mesh, const char *name) { + const node_t n = {.name = (char *)name}; + node_t *result; - n.name = name; - - pthread_mutex_lock(&(mesh->nodes_mutex)); result = splay_search(mesh->nodes, &n); - pthread_mutex_unlock(&(mesh->nodes_mutex)); return result; } @@ -119,7 +112,7 @@ node_t *lookup_node_udp(meshlink_handle_t *mesh, const sockaddr_t *sa) { void update_node_udp(meshlink_handle_t *mesh, node_t *n, const sockaddr_t *sa) { if(n == mesh->self) { - logger(DEBUG_ALWAYS, LOG_WARNING, "Trying to update UDP address of mesh->self!"); + logger(mesh, MESHLINK_WARNING, "Trying to update UDP address of mesh->self!"); return; } @@ -137,6 +130,6 @@ void update_node_udp(meshlink_handle_t *mesh, node_t *n, const sockaddr_t *sa) { hash_insert(mesh->node_udp_cache, sa, n); free(n->hostname); n->hostname = sockaddr2hostname(&n->address); - logger(DEBUG_PROTOCOL, LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); + logger(mesh, MESHLINK_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); } } diff --git a/src/node.h b/src/node.h index 427bc83a..5761e4d7 100644 --- a/src/node.h +++ b/src/node.h @@ -44,6 +44,7 @@ typedef struct node_t { void *priv; uint32_t options; /* options turned on for this node */ + dev_class_t devclass; struct meshlink_handle *mesh; /* The mesh this node belongs to */ @@ -69,6 +70,8 @@ typedef struct node_t { struct splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */ struct connection_t *connection; /* Connection associated with this node (if a direct connection exists) */ + time_t last_connect_try; + time_t last_successfull_connection; uint32_t sent_seqno; /* Sequence number last sent to this node */ uint32_t received_seqno; /* Sequence number last received from this node */ @@ -102,7 +105,7 @@ extern node_t *new_node(void) __attribute__ ((__malloc__)); extern void free_node(node_t *); extern void node_add(struct meshlink_handle *mesh, node_t *); extern void node_del(struct meshlink_handle *mesh, node_t *); -extern node_t *lookup_node(struct meshlink_handle *mesh, char *); +extern node_t *lookup_node(struct meshlink_handle *mesh, const char *); extern node_t *lookup_node_udp(struct meshlink_handle *mesh, const sockaddr_t *); extern void update_node_udp(struct meshlink_handle *mesh, node_t *, const sockaddr_t *); diff --git a/src/protocol.c b/src/protocol.c index 4cf35155..e22053c7 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -77,12 +77,12 @@ bool send_request(meshlink_handle_t *mesh, connection_t *c, const char *format, va_end(args); if(len < 0 || len > MAXBUFSIZE - 1) { - logger(DEBUG_ALWAYS, LOG_ERR, "Output buffer overflow while sending request to %s (%s)", + logger(mesh, MESHLINK_ERROR, "Output buffer overflow while sending request to %s (%s)", c->name, c->hostname); return false; } - logger(DEBUG_META, LOG_DEBUG, "Sending %s to %s (%s): %s", request_name[atoi(request)], c->name, c->hostname, request); + logger(mesh, MESHLINK_DEBUG, "Sending %s to %s (%s): %s", request_name[atoi(request)], c->name, c->hostname, request); request[len++] = '\n'; @@ -94,7 +94,7 @@ bool send_request(meshlink_handle_t *mesh, connection_t *c, const char *format, } void forward_request(meshlink_handle_t *mesh, connection_t *from, const char *request) { - logger(DEBUG_META, LOG_DEBUG, "Forwarding %s from %s (%s): %s", request_name[atoi(request)], from->name, from->hostname, request); + logger(mesh, MESHLINK_DEBUG, "Forwarding %s from %s (%s): %s", request_name[atoi(request)], from->name, from->hostname, request); // Create a temporary newline-terminated copy of the request int len = strlen(request); @@ -110,10 +110,10 @@ bool receive_request(meshlink_handle_t *mesh, connection_t *c, const char *reque return true; if(!strncasecmp(request, "HTTP/1.1 ", 9)) { if(!strncmp(request + 9, "200", 3)) { - logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted"); + logger(mesh, MESHLINK_DEBUG, "Proxy request granted"); return true; } else { - logger(DEBUG_ALWAYS, LOG_DEBUG, "Proxy request rejected: %s", request + 9); + logger(mesh, MESHLINK_DEBUG, "Proxy request rejected: %s", request + 9); return false; } } @@ -123,25 +123,25 @@ bool receive_request(meshlink_handle_t *mesh, connection_t *c, const char *reque if(reqno || *request == '0') { if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) { - logger(DEBUG_META, LOG_DEBUG, "Unknown request from %s (%s): %s", c->name, c->hostname, request); + logger(mesh, MESHLINK_DEBUG, "Unknown request from %s (%s): %s", c->name, c->hostname, request); return false; } else { - logger(DEBUG_META, LOG_DEBUG, "Got %s from %s (%s): %s", request_name[reqno], c->name, c->hostname, request); + logger(mesh, MESHLINK_DEBUG, "Got %s from %s (%s): %s", request_name[reqno], c->name, c->hostname, request); } if((c->allow_request != ALL) && (c->allow_request != reqno)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Unauthorized request from %s (%s)", c->name, c->hostname); + logger(mesh, MESHLINK_ERROR, "Unauthorized request from %s (%s)", c->name, c->hostname); return false; } if(!request_handlers[reqno](mesh, c, request)) { /* Something went wrong. Probably scriptkiddies. Terminate. */ - logger(DEBUG_ALWAYS, LOG_ERR, "Error while processing %s from %s (%s)", request_name[reqno], c->name, c->hostname); + logger(mesh, MESHLINK_ERROR, "Error while processing %s from %s (%s)", request_name[reqno], c->name, c->hostname); return false; } } else { - logger(DEBUG_ALWAYS, LOG_ERR, "Bogus data received from %s (%s)", c->name, c->hostname); + logger(mesh, MESHLINK_ERROR, "Bogus data received from %s (%s)", c->name, c->hostname); return false; } @@ -171,7 +171,7 @@ static void age_past_requests(event_loop_t *loop, void *data) { } if(left || deleted) - logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Aging past requests: deleted %d, left %d", deleted, left); + logger(mesh, MESHLINK_DEBUG, "Aging past requests: deleted %d, left %d", deleted, left); if(left) timeout_set(&mesh->loop, &mesh->past_request_timeout, &(struct timeval){10, rand() % 100000}); @@ -183,7 +183,7 @@ bool seen_request(meshlink_handle_t *mesh, const char *request) { p.request = request; if(splay_search(mesh->past_request_tree, &p)) { - logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Already seen request"); + logger(mesh, MESHLINK_DEBUG, "Already seen request"); return true; } else { new = xmalloc(sizeof *new); @@ -200,7 +200,9 @@ void init_requests(meshlink_handle_t *mesh) { } void exit_requests(meshlink_handle_t *mesh) { - splay_delete_tree(mesh->past_request_tree); + if(mesh->past_request_tree) + splay_delete_tree(mesh->past_request_tree); + mesh->past_request_tree = NULL; timeout_del(&mesh->loop, &mesh->past_request_timeout); } diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 9bd5ffca..404a81c8 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -37,6 +37,10 @@ #include "xalloc.h" #include "ed25519/sha512.h" +#include + +extern bool node_write_devclass(meshlink_handle_t *mesh, node_t *n); + static bool send_proxyrequest(meshlink_handle_t *mesh, connection_t *c) { switch(mesh->proxytype) { case PROXY_HTTP: { @@ -51,7 +55,7 @@ static bool send_proxyrequest(meshlink_handle_t *mesh, connection_t *c) { } case PROXY_SOCKS4: { if(c->address.sa.sa_family != AF_INET) { - logger(DEBUG_ALWAYS, LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!"); + logger(mesh, MESHLINK_ERROR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!"); return false; } char s4req[9 + (mesh->proxyuser ? strlen(mesh->proxyuser) : 0)]; @@ -105,7 +109,7 @@ static bool send_proxyrequest(meshlink_handle_t *mesh, connection_t *c) { i += 2; c->tcplen += 22; } else { - logger(DEBUG_ALWAYS, LOG_ERR, "Address family %hx not supported for SOCKS 5 proxies!", c->address.sa.sa_family); + logger(mesh, MESHLINK_ERROR, "Address family %hx not supported for SOCKS 5 proxies!", c->address.sa.sa_family); return false; } if(i > len) @@ -113,19 +117,18 @@ static bool send_proxyrequest(meshlink_handle_t *mesh, connection_t *c) { return send_meta(mesh, c, s5req, sizeof s5req); } case PROXY_SOCKS4A: - logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type not implemented yet"); + logger(mesh, MESHLINK_ERROR, "Proxy type not implemented yet"); return false; case PROXY_EXEC: return true; default: - logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type"); + logger(mesh, MESHLINK_ERROR, "Unknown proxy type"); return false; } } bool send_id(meshlink_handle_t *mesh, connection_t *c) { - gettimeofday(&c->start, NULL); - + int minor = mesh->self->connection->protocol_minor; if(mesh->proxytype && c->outgoing) @@ -137,7 +140,7 @@ bool send_id(meshlink_handle_t *mesh, connection_t *c) { static bool finalize_invitation(meshlink_handle_t *mesh, connection_t *c, const void *data, uint16_t len) { if(strchr(data, '\n')) { - logger(DEBUG_ALWAYS, LOG_ERR, "Received invalid key from invited node %s (%s)!\n", c->name, c->hostname); + logger(mesh, MESHLINK_ERROR, "Received invalid key from invited node %s (%s)!\n", c->name, c->hostname); return false; } @@ -145,20 +148,20 @@ static bool finalize_invitation(meshlink_handle_t *mesh, connection_t *c, const char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, c->name); if(!access(filename, F_OK)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Host config file for %s (%s) already exists!\n", c->name, c->hostname); + logger(mesh, MESHLINK_ERROR, "Host config file for %s (%s) already exists!\n", c->name, c->hostname); return false; } FILE *f = fopen(filename, "w"); if(!f) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to create %s: %s\n", filename, strerror(errno)); + logger(mesh, MESHLINK_ERROR, "Error trying to create %s: %s\n", filename, strerror(errno)); return false; } fprintf(f, "ECDSAPublicKey = %s\n", (const char *)data); fclose(f); - logger(DEBUG_CONNECTIONS, LOG_INFO, "Key succesfully received from %s (%s)", c->name, c->hostname); + logger(mesh, MESHLINK_INFO, "Key succesfully received from %s (%s)", c->name, c->hostname); //TODO: callback to application to inform of an accepted invitation @@ -200,16 +203,16 @@ static bool receive_invitation_sptps(void *handle, uint8_t type, const void *dat // Atomically rename the invitation file if(rename(filename, usedname)) { if(errno == ENOENT) - logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s tried to use non-existing invitation %s\n", c->hostname, cookie); + logger(mesh, MESHLINK_ERROR, "Peer %s tried to use non-existing invitation %s\n", c->hostname, cookie); else - logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to rename invitation %s\n", cookie); + logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", cookie); return false; } // Open the renamed file FILE *f = fopen(usedname, "r"); if(!f) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to open invitation %s\n", cookie); + logger(mesh, MESHLINK_ERROR, "Error trying to open invitation %s\n", cookie); return false; } @@ -229,7 +232,7 @@ static bool receive_invitation_sptps(void *handle, uint8_t type, const void *dat buf[len] = 0; if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Invalid invitation file %s\n", cookie); + logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", cookie); fclose(f); return false; } @@ -248,7 +251,7 @@ static bool receive_invitation_sptps(void *handle, uint8_t type, const void *dat c->status.invitation_used = true; - logger(DEBUG_CONNECTIONS, LOG_INFO, "Invitation %s succesfully sent to %s (%s)", cookie, c->name, c->hostname); + logger(mesh, MESHLINK_INFO, "Invitation %s succesfully sent to %s (%s)", cookie, c->name, c->hostname); return true; } @@ -256,7 +259,7 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { char name[MAX_STRING_SIZE]; if(sscanf(request, "%*d " MAX_STRING " %d.%d", name, &c->protocol_major, &c->protocol_minor) < 2) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "ID", c->name, c->hostname); return false; } @@ -265,13 +268,13 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { if(name[0] == '?') { if(!mesh->invitation_key) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got invitation from %s but we don't have an invitation key", c->hostname); + logger(mesh, MESHLINK_ERROR, "Got invitation from %s but we don't have an invitation key", c->hostname); return false; } c->ecdsa = ecdsa_set_base64_public_key(name + 1); if(!c->ecdsa) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad invitation from %s", c->hostname); + logger(mesh, MESHLINK_ERROR, "Got bad invitation from %s", c->hostname); return false; } @@ -292,7 +295,7 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { /* Check if identity is a valid name */ if(!check_id(name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ID", c->name, c->hostname, "invalid name"); return false; } @@ -301,7 +304,7 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { if(c->outgoing) { if(strcmp(c->name, name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name, + logger(mesh, MESHLINK_ERROR, "Peer %s is %s instead of %s", c->hostname, name, c->name); return false; } @@ -314,7 +317,7 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { /* Check if version matches */ if(c->protocol_major != mesh->self->connection->protocol_major) { - logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) uses incompatible version %d.%d", + logger(mesh, MESHLINK_ERROR, "Peer %s (%s) uses incompatible version %d.%d", c->name, c->hostname, c->protocol_major, c->protocol_minor); return false; } @@ -323,20 +326,29 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { init_configuration(&c->config_tree); if(!read_host_config(mesh, c->config_tree, c->name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname, c->name); + logger(mesh, MESHLINK_ERROR, "Peer %s had unknown identity (%s)", c->hostname, c->name); return false; } + } - read_ecdsa_public_key(mesh, c); - } else { - if(c->protocol_minor && !ecdsa_active(c->ecdsa)) - c->protocol_minor = 1; + read_ecdsa_public_key(mesh, c); + + if(!ecdsa_active(c->ecdsa)) { + logger(mesh, MESHLINK_ERROR, "No key known for peer %s (%s)", c->name, c->hostname); + + node_t *n = lookup_node(mesh, c->name); + if(n && !n->status.waitingforkey) { + logger(mesh, MESHLINK_INFO, "Requesting key from peer %s (%s)", c->name, c->hostname); + send_req_key(mesh, n); + } + + return false; } /* Forbid version rollback for nodes whose ECDSA key we know */ if(ecdsa_active(c->ecdsa) && c->protocol_minor < 2) { - logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) tries to roll back protocol version to %d.%d", + logger(mesh, MESHLINK_ERROR, "Peer %s (%s) tries to roll back protocol version to %d.%d", c->name, c->hostname, c->protocol_major, c->protocol_minor); return false; } @@ -353,22 +365,13 @@ bool id_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { } bool send_ack(meshlink_handle_t *mesh, connection_t *c) { - /* ACK message contains rest of the information the other end needs - to create node_t and edge_t structures. */ - - struct timeval now; - - /* Estimate weight */ - - gettimeofday(&now, NULL); - c->estimated_weight = (now.tv_sec - c->start.tv_sec) * 1000 + (now.tv_usec - c->start.tv_usec) / 1000; /* Check some options */ if(mesh->self->options & OPTION_PMTU_DISCOVERY) c->options |= OPTION_PMTU_DISCOVERY; - return send_request(mesh, c, "%d %s %d %x", ACK, mesh->myport, c->estimated_weight, (c->options & 0xffffff) | (PROT_MINOR << 24)); + return send_request(mesh, c, "%d %s %d %x", ACK, mesh->myport, mesh->devclass, (c->options & 0xffffff) | (PROT_MINOR << 24)); } static void send_everything(meshlink_handle_t *mesh, connection_t *c) { @@ -383,16 +386,22 @@ static void send_everything(meshlink_handle_t *mesh, connection_t *c) { bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { char hisport[MAX_STRING_SIZE]; char *hisaddress; - int weight; + int devclass; uint32_t options; node_t *n; - if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, + if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &devclass, &options) != 3) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname); return false; } + if(devclass < 0 || devclass > _DEV_CLASS_MAX) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ACK", c->name, + c->hostname, "devclass invalid"); + return false; + } + /* Check if we already have a node_t for him */ n = lookup_node(mesh, c->name); @@ -404,11 +413,11 @@ bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { } else { if(n->connection) { /* Oh dear, we already have a connection to this node. */ - logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname); + logger(mesh, MESHLINK_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname); if(n->connection->outgoing) { if(c->outgoing) - logger(DEBUG_ALWAYS, LOG_WARNING, "Two outgoing connections to the same node!"); + logger(mesh, MESHLINK_WARNING, "Two outgoing connections to the same node!"); else c->outgoing = n->connection->outgoing; @@ -421,6 +430,11 @@ bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { } } + n->devclass = devclass; + node_write_devclass(mesh, n); + + n->last_successfull_connection = time(NULL); + n->connection = c; c->node = n; if(!(c->options & options & OPTION_PMTU_DISCOVERY)) { @@ -434,7 +448,7 @@ bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { c->allow_request = ALL; c->status.active = true; - logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection with %s (%s) activated", c->name, + logger(mesh, MESHLINK_INFO, "Connection with %s (%s) activated", c->name, c->hostname); /* Send him everything we know */ @@ -443,13 +457,15 @@ bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { /* Create an edge_t for this connection */ + assert(devclass >= 0 && devclass <= _DEV_CLASS_MAX); + c->edge = new_edge(); c->edge->from = mesh->self; c->edge->to = n; sockaddr2str(&c->address, &hisaddress, NULL); c->edge->address = str2sockaddr(hisaddress, hisport); free(hisaddress); - c->edge->weight = (weight + c->estimated_weight) / 2; + c->edge->weight = dev_class_traits[devclass].edge_weight; c->edge->connection = c; c->edge->options = c->options; diff --git a/src/protocol_edge.c b/src/protocol_edge.c index 3b89cdec..89ed901d 100644 --- a/src/protocol_edge.c +++ b/src/protocol_edge.c @@ -33,14 +33,16 @@ #include "utils.h" #include "xalloc.h" +extern bool node_write_devclass(meshlink_handle_t *mesh, node_t *n); + bool send_add_edge(meshlink_handle_t *mesh, connection_t *c, const edge_t *e) { bool x; char *address, *port; sockaddr2str(&e->address, &address, &port); - x = send_request(mesh, c, "%d %x %s %s %s %s %x %d", ADD_EDGE, rand(), - e->from->name, e->to->name, address, port, + x = send_request(mesh, c, "%d %x %s %d %s %s %s %d %x %d", ADD_EDGE, rand(), + e->from->name, e->from->devclass, e->to->name, address, port, e->to->devclass, e->options, e->weight); free(address); free(port); @@ -52,16 +54,18 @@ bool add_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { edge_t *e; node_t *from, *to; char from_name[MAX_STRING_SIZE]; + int from_devclass; char to_name[MAX_STRING_SIZE]; char to_address[MAX_STRING_SIZE]; char to_port[MAX_STRING_SIZE]; + int to_devclass; sockaddr_t address; uint32_t options; int weight; - if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d", - from_name, to_name, to_address, to_port, &options, &weight) != 6) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name, + if(sscanf(request, "%*d %*x "MAX_STRING" %d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %x %d", + from_name, &from_devclass, to_name, to_address, to_port, &to_devclass, &options, &weight) != 8) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name, c->hostname); return false; } @@ -69,11 +73,25 @@ bool add_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { /* Check if names are valid */ if(!check_id(from_name) || !check_id(to_name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, c->hostname, "invalid name"); return false; } + // Check if devclasses are valid + + if(from_devclass < 0 || from_devclass > _DEV_CLASS_MAX) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, + c->hostname, "from devclass invalid"); + return false; + } + + if(to_devclass < 0 || to_devclass > _DEV_CLASS_MAX) { + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, + c->hostname, "to devclass invalid"); + return false; + } + if(seen_request(mesh, request)) return true; @@ -88,12 +106,17 @@ bool add_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { node_add(mesh, from); } + from->devclass = from_devclass; + node_write_devclass(mesh, from); + if(!to) { to = new_node(); to->name = xstrdup(to_name); node_add(mesh, to); } + to->devclass = to_devclass; + node_write_devclass(mesh, to); /* Convert addresses */ @@ -106,12 +129,12 @@ bool add_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { if(e) { if(e->weight != weight || e->options != options || sockaddrcmp(&e->address, &address)) { if(from == mesh->self) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry", "ADD_EDGE", c->name, c->hostname); send_add_edge(mesh, c, e); return true; } else { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not match existing entry", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) which does not match existing entry", "ADD_EDGE", c->name, c->hostname); edge_del(mesh, e); graph(mesh); @@ -119,7 +142,7 @@ bool add_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { } else return true; } else if(from == mesh->self) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) for ourself which does not exist", "ADD_EDGE", c->name, c->hostname); mesh->contradicting_add_edge++; e = new_edge(); @@ -161,7 +184,7 @@ bool del_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { node_t *from, *to; if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name, c->hostname); return false; } @@ -169,7 +192,7 @@ bool del_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { /* Check if names are valid */ if(!check_id(from_name) || !check_id(to_name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name, c->hostname, "invalid name"); return false; } @@ -183,13 +206,13 @@ bool del_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { to = lookup_node(mesh, to_name); if(!from) { - logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) which does not appear in the edge tree", "DEL_EDGE", c->name, c->hostname); return true; } if(!to) { - logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) which does not appear in the edge tree", "DEL_EDGE", c->name, c->hostname); return true; } @@ -199,13 +222,13 @@ bool del_edge_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { e = lookup_edge(from, to); if(!e) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) which does not appear in the edge tree", "DEL_EDGE", c->name, c->hostname); return true; } if(e->from == mesh->self) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) for ourself", "DEL_EDGE", c->name, c->hostname); mesh->contradicting_del_edge++; send_add_edge(mesh, c, e); /* Send back a correction */ diff --git a/src/protocol_key.c b/src/protocol_key.c index 97e19610..e138dd6b 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -46,7 +46,7 @@ bool key_changed_h(meshlink_handle_t *mesh, connection_t *c, const char *request node_t *n; if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED", + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "KEY_CHANGED", c->name, c->hostname); return false; } @@ -57,7 +57,7 @@ bool key_changed_h(meshlink_handle_t *mesh, connection_t *c, const char *request n = lookup_node(mesh, name); if(!n) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) origin %s which does not exist", "KEY_CHANGED", c->name, c->hostname, name); return true; } @@ -80,13 +80,13 @@ static bool send_initial_sptps_data(void *handle, uint8_t type, const void *data bool send_req_key(meshlink_handle_t *mesh, node_t *to) { if(!node_read_ecdsa_public_key(mesh, to)) { - logger(DEBUG_PROTOCOL, LOG_DEBUG, "No ECDSA key known for %s (%s)", to->name, to->hostname); + logger(mesh, MESHLINK_DEBUG, "No ECDSA key known for %s (%s)", to->name, to->hostname); send_request(mesh, to->nexthop->connection, "%d %s %s %d", REQ_KEY, mesh->self->name, to->name, REQ_PUBKEY); return true; } if(to->sptps.label) - logger(DEBUG_ALWAYS, LOG_DEBUG, "send_req_key(%s) called while sptps->label != NULL!", to->name); + logger(mesh, MESHLINK_DEBUG, "send_req_key(%s) called while sptps->label != NULL!", to->name); char label[25 + strlen(mesh->self->name) + strlen(to->name)]; snprintf(label, sizeof label, "MeshLink UDP key expansion %s %s", mesh->self->name, to->name); @@ -111,36 +111,36 @@ static bool req_key_ext_h(meshlink_handle_t *mesh, connection_t *c, const char * case ANS_PUBKEY: { if(node_read_ecdsa_public_key(mesh, from)) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got ANS_PUBKEY from %s (%s) even though we already have his pubkey", from->name, from->hostname); + logger(mesh, MESHLINK_WARNING, "Got ANS_PUBKEY from %s (%s) even though we already have his pubkey", from->name, from->hostname); return true; } char pubkey[MAX_STRING_SIZE]; if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, pubkey) != 1 || !(from->ecdsa = ecdsa_set_base64_public_key(pubkey))) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_PUBKEY", from->name, from->hostname, "invalid pubkey"); + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ANS_PUBKEY", from->name, from->hostname, "invalid pubkey"); return true; } - logger(DEBUG_PROTOCOL, LOG_INFO, "Learned ECDSA public key from %s (%s)", from->name, from->hostname); + logger(mesh, MESHLINK_INFO, "Learned ECDSA public key from %s (%s)", from->name, from->hostname); append_config_file(mesh, from->name, "ECDSAPublicKey", pubkey); return true; } case REQ_KEY: { if(!node_read_ecdsa_public_key(mesh, from)) { - logger(DEBUG_PROTOCOL, LOG_DEBUG, "No ECDSA key known for %s (%s)", from->name, from->hostname); + logger(mesh, MESHLINK_DEBUG, "No ECDSA key known for %s (%s)", from->name, from->hostname); send_request(mesh, from->nexthop->connection, "%d %s %s %d", REQ_KEY, mesh->self->name, from->name, REQ_PUBKEY); return true; } if(from->sptps.label) - logger(DEBUG_ALWAYS, LOG_DEBUG, "Got REQ_KEY from %s while we already started a SPTPS session!", from->name); + logger(mesh, MESHLINK_DEBUG, "Got REQ_KEY from %s while we already started a SPTPS session!", from->name); char buf[MAX_STRING_SIZE]; int len; if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1 || !(len = b64decode(buf, buf, strlen(buf)))) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_SPTPS_START", from->name, from->hostname, "invalid SPTPS data"); + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "REQ_SPTPS_START", from->name, from->hostname, "invalid SPTPS data"); return true; } @@ -157,14 +157,14 @@ static bool req_key_ext_h(meshlink_handle_t *mesh, connection_t *c, const char * case REQ_SPTPS: { if(!from->status.validkey) { - logger(DEBUG_PROTOCOL, LOG_ERR, "Got REQ_SPTPS from %s (%s) but we don't have a valid key yet", from->name, from->hostname); + logger(mesh, MESHLINK_ERROR, "Got REQ_SPTPS from %s (%s) but we don't have a valid key yet", from->name, from->hostname); return true; } char buf[MAX_STRING_SIZE]; int len; if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1 || !(len = b64decode(buf, buf, strlen(buf)))) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_SPTPS", from->name, from->hostname, "invalid SPTPS data"); + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "REQ_SPTPS", from->name, from->hostname, "invalid SPTPS data"); return true; } sptps_receive_data(&from->sptps, buf, len); @@ -172,7 +172,7 @@ static bool req_key_ext_h(meshlink_handle_t *mesh, connection_t *c, const char * } default: - logger(DEBUG_ALWAYS, LOG_ERR, "Unknown extended REQ_KEY request from %s (%s): %s", from->name, from->hostname, request); + logger(mesh, MESHLINK_ERROR, "Unknown extended REQ_KEY request from %s (%s): %s", from->name, from->hostname, request); return true; } } @@ -184,20 +184,20 @@ bool req_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { int reqno = 0; if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &reqno) < 2) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "REQ_KEY", c->name, c->hostname); return false; } if(!check_id(from_name) || !check_id(to_name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name"); + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name"); return false; } from = lookup_node(mesh, from_name); if(!from) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "REQ_KEY", c->name, c->hostname, from_name); return true; } @@ -205,7 +205,7 @@ bool req_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { to = lookup_node(mesh, to_name); if(!to) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "REQ_KEY", c->name, c->hostname, to_name); return true; } @@ -221,7 +221,7 @@ bool req_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { send_ans_key(mesh, from); } else { if(!to->status.reachable) { - logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) destination %s which is not reachable", "REQ_KEY", c->name, c->hostname, to_name); return true; } @@ -248,20 +248,20 @@ bool ans_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d "MAX_STRING" "MAX_STRING, from_name, to_name, key, &cipher, &digest, &maclength, &compression, address, port) < 7) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "ANS_KEY", c->name, c->hostname); return false; } if(!check_id(from_name) || !check_id(to_name)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name"); + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name"); return false; } from = lookup_node(mesh, from_name); if(!from) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "ANS_KEY", c->name, c->hostname, from_name); return true; } @@ -269,7 +269,7 @@ bool ans_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { to = lookup_node(mesh, to_name); if(!to) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", + logger(mesh, MESHLINK_ERROR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "ANS_KEY", c->name, c->hostname, to_name); return true; } @@ -278,14 +278,14 @@ bool ans_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { if(to != mesh->self) { if(!to->status.reachable) { - logger(DEBUG_ALWAYS, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", + logger(mesh, MESHLINK_WARNING, "Got %s from %s (%s) destination %s which is not reachable", "ANS_KEY", c->name, c->hostname, to_name); return true; } if(!*address && from->address.sa.sa_family != AF_UNSPEC) { char *address, *port; - logger(DEBUG_PROTOCOL, LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); + logger(mesh, MESHLINK_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); sockaddr2str(&from->address, &address, &port); send_request(mesh, to->nexthop->connection, "%s %s %s", request, address, port); free(address); @@ -300,7 +300,7 @@ bool ans_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { from->status.validkey = false; if(compression < 0 || compression > 11) { - logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname); + logger(mesh, MESHLINK_ERROR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname); return true; } @@ -312,11 +312,11 @@ bool ans_key_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { int len = b64decode(key, buf, strlen(key)); if(!len || !sptps_receive_data(&from->sptps, buf, len)) - logger(DEBUG_ALWAYS, LOG_ERR, "Error processing SPTPS data from %s (%s)", from->name, from->hostname); + logger(mesh, MESHLINK_ERROR, "Error processing SPTPS data from %s (%s)", from->name, from->hostname); if(from->status.validkey) { if(*address && *port) { - logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + logger(mesh, MESHLINK_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); sockaddr_t sa = str2sockaddr(address, port); update_node_udp(mesh, from, &sa); } diff --git a/src/protocol_misc.c b/src/protocol_misc.c index 84a2531c..7e9ebe50 100644 --- a/src/protocol_misc.c +++ b/src/protocol_misc.c @@ -45,12 +45,12 @@ bool status_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { char statusstring[MAX_STRING_SIZE]; if(sscanf(request, "%*d %d " MAX_STRING, &statusno, statusstring) != 2) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "STATUS", + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "STATUS", c->name, c->hostname); return false; } - logger(DEBUG_STATUS, LOG_NOTICE, "Status message from %s (%s): %d: %s", + logger(mesh, MESHLINK_INFO, "Status message from %s (%s): %d: %s", c->name, c->hostname, statusno, statusstring); return true; @@ -68,12 +68,12 @@ bool error_h(meshlink_handle_t *mesh, connection_t *c, const char *request) { char errorstring[MAX_STRING_SIZE]; if(sscanf(request, "%*d %d " MAX_STRING, &err, errorstring) != 2) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ERROR", + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "ERROR", c->name, c->hostname); return false; } - logger(DEBUG_ERROR, LOG_NOTICE, "Error message from %s (%s): %d: %s", + logger(mesh, MESHLINK_INFO, "Error message from %s (%s): %d: %s", c->name, c->hostname, err, errorstring); return false; @@ -138,7 +138,7 @@ bool tcppacket_h(meshlink_handle_t *mesh, connection_t *c, const char *request) short int len; if(sscanf(request, "%*d %hd", &len) != 1) { - logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name, + logger(mesh, MESHLINK_ERROR, "Got bad %s from %s (%s)", "PACKET", c->name, c->hostname); return false; } diff --git a/src/route.c b/src/route.c index de81c2b6..f451d8e3 100644 --- a/src/route.c +++ b/src/route.c @@ -29,9 +29,7 @@ bool decrement_ttl = false; static bool checklength(node_t *source, vpn_packet_t *packet, uint16_t length) { if(packet->len < length) { - logger(DEBUG_TRAFFIC, LOG_WARNING, - "Got too short packet from %s (%s)", source->name, - source->hostname); + logger(source->mesh, MESHLINK_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname); return false; } else return true; @@ -44,9 +42,7 @@ void route(meshlink_handle_t *mesh, node_t *source, vpn_packet_t *packet) { node_t *via = NULL; meshlink_packethdr_t *hdr = (meshlink_packethdr_t *) packet->data; owner = lookup_node(mesh, (char *)hdr->destination); - logger(DEBUG_TRAFFIC, LOG_WARNING, - "Routing packet from: %s . To: %s \n", hdr->source, - hdr->destination); + logger(mesh, MESHLINK_DEBUG, "Routing packet from \"%s\" to \"%s\"\n", hdr->source, hdr->destination); //Check Lenght if(!checklength(source, packet, (sizeof(meshlink_packethdr_t)))) @@ -54,13 +50,13 @@ void route(meshlink_handle_t *mesh, node_t *source, vpn_packet_t *packet) { if(owner == NULL) { //Lookup failed - logger(DEBUG_TRAFFIC, LOG_WARNING, "Cant lookup the owner of a packet in the route() function. This should never happen!\n"); - logger(DEBUG_TRAFFIC, LOG_WARNING, "Destination was: %s\n", hdr->destination); + logger(mesh, MESHLINK_WARNING, "Cant lookup the owner of a packet in the route() function. This should never happen!\n"); + logger(mesh, MESHLINK_WARNING, "Destination was: %s\n", hdr->destination); return; } if(owner == mesh->self) { - logger(DEBUG_TRAFFIC, LOG_WARNING, "I received a packet for me with payload: %s \n", packet->data + sizeof *hdr); + logger(mesh, MESHLINK_DEBUG, "I received a packet for me with payload: %s \n", packet->data + sizeof *hdr); if(mesh->receive_cb) mesh->receive_cb(mesh, (meshlink_node_t *)source, packet->data + sizeof *hdr, packet->len - sizeof *hdr); return; @@ -68,13 +64,13 @@ void route(meshlink_handle_t *mesh, node_t *source, vpn_packet_t *packet) { if(!owner->status.reachable) { //TODO: check what to do here, not just print a warning - logger(DEBUG_TRAFFIC, LOG_WARNING, "The owner of a packet in the route() function is unreachable. Dropping packet.\n"); + logger(mesh, MESHLINK_WARNING, "The owner of a packet in the route() function is unreachable. Dropping packet.\n"); return; } via = (owner->via == mesh->self) ? owner->nexthop : owner->via; if(via == source) { - logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); + logger(mesh, MESHLINK_ERROR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); return; } diff --git a/src/sockaddr.h b/src/sockaddr.h index 255dc830..775f81f6 100644 --- a/src/sockaddr.h +++ b/src/sockaddr.h @@ -22,9 +22,7 @@ typedef union sockaddr_t { struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_unknown unknown; -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE struct sockaddr_storage storage; -#endif } sockaddr_t; #endif // SOCKADDR_H diff --git a/src/sptps.c b/src/sptps.c index e03b18a8..2e9ac6fe 100644 --- a/src/sptps.c +++ b/src/sptps.c @@ -371,12 +371,20 @@ static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) { // Check datagram for valid HMAC bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len) { - if(!s->instate || len < 21) - return error(s, EIO, "Received short packet"); + if (!s->instate) + return error(s, EIO, "SPTPS state not ready to verify this datagram"); - // TODO: just decrypt without updating the replay window + if(len < 21) + return error(s, EIO, "Received short packet in sptps_verify_datagram"); - return true; + uint32_t seqno; + memcpy(&seqno, data, 4); + seqno = ntohl(seqno); + // TODO: check whether seqno makes sense, to avoid CPU intensive decrypt + + char buffer[len]; + size_t outlen; + return chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen); } // Receive incoming data, datagram version. @@ -384,7 +392,7 @@ static bool sptps_receive_data_datagram(sptps_t *s, const void *vdata, size_t le const char *data = vdata; if(len < (s->instate ? 21 : 5)) - return error(s, EIO, "Received short packet"); + return error(s, EIO, "Received short packet in sptps_receive_data_datagram"); uint32_t seqno; memcpy(&seqno, data, 4); @@ -557,6 +565,9 @@ bool sptps_receive_data(sptps_t *s, const void *data, size_t len) { // Start a SPTPS session. bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) { + if(!s || !mykey || !hiskey || !label || !labellen || !send_data || !receive_record) + return error(s, EINVAL, "Invalid argument to sptps_start()"); + // Initialise struct sptps memset(s, 0, sizeof *s); diff --git a/src/sptps_speed.c b/src/sptps_speed.c index 19ac6263..c27683b5 100644 --- a/src/sptps_speed.c +++ b/src/sptps_speed.c @@ -30,6 +30,8 @@ // Symbols necessary to link with logger.o bool send_request(void *c, const char *msg, ...) { return false; } void *mesh; +void *global_log_cb; +int global_log_level; bool send_meta(void *c, const char *msg , int len) { return false; } char *logfilename = NULL; struct timeval now; diff --git a/src/sptps_test.c b/src/sptps_test.c index 550e0db2..b982f387 100644 --- a/src/sptps_test.c +++ b/src/sptps_test.c @@ -35,6 +35,8 @@ // Symbols necessary to link with logger.o bool send_request(void *c, const char *msg, ...) { return false; } void *mesh; +void *global_log_cb; +int global_log_level; bool send_meta(void *c, const char *msg , int len) { return false; } char *logfilename = NULL; struct timeval now; diff --git a/test/basic.c b/test/basic.c index 9b09455a..002c5769 100644 --- a/test/basic.c +++ b/test/basic.c @@ -6,7 +6,7 @@ int main(int argc, char *argv[]) { // Open a new meshlink instance. - meshlink_handle_t *mesh = meshlink_open("basic_conf", "foo"); + meshlink_handle_t *mesh = meshlink_open("basic_conf", "foo", "basic"); if(!mesh) { fprintf(stderr, "Could not initialize configuration for foo\n"); return 1; @@ -46,7 +46,7 @@ int main(int argc, char *argv[]) { // Check that the name is ignored now, and that we still are "foo". - mesh = meshlink_open("basic_conf", "bar"); + mesh = meshlink_open("basic_conf", "bar", "basic"); if(!mesh) { fprintf(stderr, "Could not open configuration for foo a second time\n"); return 1; diff --git a/test/basicpp.cpp b/test/basicpp.cpp index d82610e3..e1c10366 100644 --- a/test/basicpp.cpp +++ b/test/basicpp.cpp @@ -8,7 +8,7 @@ using namespace std; int main(int argc, char *argv[]) { // Open a new meshlink instance. - meshlink::mesh *mesh = meshlink::open("basicpp_conf", "foo"); + meshlink::mesh *mesh = meshlink::open("basicpp_conf", "foo", "basicpp"); if(!mesh) { cerr << "Could not initialize configuration for foo\n"; return 1; @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) { // Check that the name is ignored now, and that we still are "foo". - mesh = meshlink::open("basic_conf", "bar"); + mesh = meshlink::open("basic_conf", "bar", "basicpp"); if(!mesh) { cerr << "Could not open configuration for foo a second time\n"; return 1; diff --git a/test/channels.c b/test/channels.c index 8327e8ed..280902cb 100644 --- a/test/channels.c +++ b/test/channels.c @@ -35,13 +35,13 @@ bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_no int main(int argc, char *argv[]) { // Open two new meshlink instance. - meshlink_handle_t *mesh1 = meshlink_open("channels_conf.1", "foo"); + meshlink_handle_t *mesh1 = meshlink_open("channels_conf.1", "foo", "channels"); if(!mesh1) { fprintf(stderr, "Could not initialize configuration for foo\n"); return 1; } - meshlink_handle_t *mesh2 = meshlink_open("channels_conf.2", "bar"); + meshlink_handle_t *mesh2 = meshlink_open("channels_conf.2", "bar", "channels"); if(!mesh2) { fprintf(stderr, "Could not initialize configuration for bar\n"); return 1; diff --git a/test/import-export.c b/test/import-export.c index 17beaaf0..f2229589 100644 --- a/test/import-export.c +++ b/test/import-export.c @@ -15,13 +15,13 @@ void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { int main(int argc, char *argv[]) { // Open two new meshlink instance. - meshlink_handle_t *mesh1 = meshlink_open("import_export_conf.1", "foo"); + meshlink_handle_t *mesh1 = meshlink_open("import_export_conf.1", "foo", "import-export"); if(!mesh1) { fprintf(stderr, "Could not initialize configuration for foo\n"); return 1; } - meshlink_handle_t *mesh2 = meshlink_open("import_export_conf.2", "bar"); + meshlink_handle_t *mesh2 = meshlink_open("import_export_conf.2", "bar", "import-export"); if(!mesh2) { fprintf(stderr, "Could not initialize configuration for bar\n"); return 1; @@ -86,6 +86,17 @@ int main(int argc, char *argv[]) { return 1; } + int pmtu = meshlink_get_pmtu(mesh2, meshlink_get_node(mesh2, "bar")); + for(int i = 0; i < 10 && !pmtu; i++) { + sleep(1); + pmtu = meshlink_get_pmtu(mesh2, meshlink_get_node(mesh2, "bar")); + } + + if(!pmtu) { + fprintf(stderr, "UDP communication with bar not possible after 10 seconds\n"); + return 1; + } + // Clean up. meshlink_stop(mesh2); diff --git a/test/invite-join.c b/test/invite-join.c index 4471091c..a7bee95c 100644 --- a/test/invite-join.c +++ b/test/invite-join.c @@ -15,13 +15,13 @@ void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { int main(int argc, char *argv[]) { // Open two new meshlink instance. - meshlink_handle_t *mesh1 = meshlink_open("invite_join_conf.1", "foo"); + meshlink_handle_t *mesh1 = meshlink_open("invite_join_conf.1", "foo", "invite-join"); if(!mesh1) { fprintf(stderr, "Could not initialize configuration for foo\n"); return 1; } - meshlink_handle_t *mesh2 = meshlink_open("invite_join_conf.2", "bar"); + meshlink_handle_t *mesh2 = meshlink_open("invite_join_conf.2", "bar", "invite-join"); if(!mesh2) { fprintf(stderr, "Could not initialize configuration for bar\n"); return 1; @@ -70,6 +70,17 @@ int main(int argc, char *argv[]) { return 1; } + int pmtu = meshlink_get_pmtu(mesh1, meshlink_get_node(mesh1, "baz")); + for(int i = 0; i < 10 && !pmtu; i++) { + sleep(1); + pmtu = meshlink_get_pmtu(mesh1, meshlink_get_node(mesh1, "baz")); + } + + if(!pmtu) { + fprintf(stderr, "UDP communication with baz not possible after 10 seconds\n"); + return 1; + } + // Clean up. meshlink_stop(mesh2); diff --git a/test/sign-verify.c b/test/sign-verify.c index a7f9a5af..6c7ccfec 100644 --- a/test/sign-verify.c +++ b/test/sign-verify.c @@ -8,13 +8,13 @@ int main(int argc, char *argv[]) { // Open two new meshlink instance. - meshlink_handle_t *mesh1 = meshlink_open("sign_verify_conf.1", "foo"); + meshlink_handle_t *mesh1 = meshlink_open("sign_verify_conf.1", "foo", "sign-verify"); if(!mesh1) { fprintf(stderr, "Could not initialize configuration for foo\n"); return 1; } - meshlink_handle_t *mesh2 = meshlink_open("sign_verify_conf.2", "bar"); + meshlink_handle_t *mesh2 = meshlink_open("sign_verify_conf.2", "bar", "sign-verify"); if(!mesh2) { fprintf(stderr, "Could not initialize configuration for bar\n"); return 1;