From: Niklas Hofmann Date: Fri, 8 Aug 2014 12:32:03 +0000 (+0200) Subject: Merge branch 'discovery' into everbase X-Git-Url: http://git.meshlink.io/?p=meshlink;a=commitdiff_plain;h=6d1ac53f5c34ece4c7a82efb432a8e1d81fcff0d;hp=-c Merge branch 'discovery' into everbase Conflicts: examples/chatpp.cc src/meshlink++.h src/meshlink.c --- 6d1ac53f5c34ece4c7a82efb432a8e1d81fcff0d diff --combined examples/chat.c index 6ffebb86,070600f4..ddb43bf7 --- a/examples/chat.c +++ b/examples/chat.c @@@ -56,16 -56,11 +56,16 @@@ static void parse_command(meshlink_hand 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", meshlink_strerror(meshlink_errno)); - else + 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"); @@@ -187,7 -182,7 +187,7 @@@ int main(int argc, char *argv[]) if(argc > 2) nick = argv[2]; - meshlink_handle_t *mesh = meshlink_open(confbase, nick); + meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat"); if(!mesh) { fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno)); return 1; diff --combined examples/chatpp.cc index 7625f657,608367a9..c94cf2bb --- a/examples/chatpp.cc +++ b/examples/chatpp.cc @@@ -4,32 -4,28 +4,32 @@@ #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); -} - -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); -} + void node_status(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; @@@ -86,7 -82,7 +86,7 @@@ static void parse_command(meshlink::mes if(!nodes) { fprintf(stderr, "Could not get list of nodes: %s\n", meshlink::strerror()); } else { - printf("%d known nodes:", nnodes); + printf("%zu known nodes:", nnodes); for(size_t i = 0; i < nnodes; i++) printf(" %s", nodes[i]->name); printf("\n"); @@@ -175,7 -171,6 +175,7 @@@ static void parse_input(meshlink::mesh printf("Message sent to '%s'.\n", destination->name); } + int main(int argc, char *argv[]) { const char *confbase = ".chat"; const char *nick = NULL; @@@ -187,12 -182,16 +187,13 @@@ if(argc > 2) nick = argv[2]; - ChatMesh* mesh = meshlink::open(confbase, nick); - meshlink::mesh *mesh = meshlink::open(confbase, nick, "chatpp"); ++ ChatMesh* mesh = meshlink::open(confbase, nick, "chatpp"); ++ if(!mesh) { 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", meshlink::strerror()); return 1; diff --combined examples/manynodes.c index a3dc1873,e2b1630c..3fa88c3b --- a/examples/manynodes.c +++ b/examples/manynodes.c @@@ -11,8 -11,28 +11,8 @@@ static int n = 100; static meshlink_handle_t **mesh; -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); -} - -static void receive(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) { - const char *msg = 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); -} - -static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) { - if(reachable) - printf("%s joined.\n", node->name); - else - printf("%s left.\n", node->name); -} +static meshlink_node_t **nodes; +static size_t nnodes; // Make all nodes know about each other by importing each others public keys and addresses. static void linkmesh() { @@@ -78,13 -98,16 +78,13 @@@ static void parse_command(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[0], nodes, 100); - if(!n) { - fprintf(stderr, "No nodes known!\n"); + nodes = meshlink_get_all_nodes(mesh[0], nodes, &nnodes); + if(!nodes) { + 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 { @@@ -199,7 -222,7 +199,7 @@@ int main(int argc, char *argv[]) snprintf(nodename, sizeof nodename, "node%d", i); snprintf(filename, sizeof filename, "%s/%s", basebase, nodename); bool itsnew = access(filename, R_OK); - mesh[i] = meshlink_open(filename, nodename); + mesh[i] = meshlink_open(filename, nodename, "manynodes"); if(itsnew) meshlink_add_address(mesh[i], "localhost"); if(!mesh[i]) { diff --combined src/discovery.c index 00000000,1f163cf6..4d1209b8 mode 000000,100644..100644 --- a/src/discovery.c +++ b/src/discovery.c @@@ -1,0 -1,527 +1,541 @@@ + + #include "meshlink_internal.h" + #include "discovery.h" + #include "sockaddr.h" + + #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); + + /* Called whenever the entry group state changes */ + switch(state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + fprintf(stderr, "Service successfully established.\n"); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + fprintf(stderr, "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 */ + fprintf(stderr, "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: + ; + } + } + + + 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); + + fprintf(stderr, "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))) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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); } + } + + static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata) + { + meshlink_handle_t *mesh = userdata; + + // asserts + assert(mesh != NULL); + + 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); + + fprintf(stderr, "Host name collision, retrying with '%s'\n", hostnamestr); + int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr); + + if(result < 0) + { + fprintf(stderr, "Failed to set new host name: %s\n", avahi_strerror(result)); + avahi_simple_poll_quit(mesh->avahi_poll); + return; + } + } + 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 */ + fprintf(stderr, "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; + } + } + + 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); + + /* 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); + + fprintf(stderr, "(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; + + fprintf(stderr, "(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); + fprintf(stderr, + "\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 = avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY) + 1; + char *node_fp = avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1; + + meshlink_node_t *node = meshlink_get_node(mesh, node_name); + + if(node != NULL) + { + fprintf(stderr, "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 = 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 = 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 + { + fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name); + } + } + else + { + fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name); + } + } + else + { + fprintf(stderr, "TXT records missing.\n"); + } + } + break; + } + + avahi_s_service_resolver_free(resolver); + } + + 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); + + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + switch (event) + { + case AVAHI_BROWSER_FAILURE: + { + fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server))); + avahi_simple_poll_quit(mesh->avahi_poll); + } + return; + + case AVAHI_BROWSER_NEW: + { + // asserts + assert(name != NULL); + assert(type != NULL); + assert(domain != NULL); + + fprintf(stderr, "(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))) + { + fprintf(stderr, "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); + + fprintf(stderr, "(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: + { + fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + } + break; + } + } + + 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; + } + + bool discovery_start(meshlink_handle_t *mesh) + { + // 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); + + // 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) + { + fprintf(stderr, "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())) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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))) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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) + { + // asserts + assert(mesh != NULL); - assert(mesh->avahi_poll != NULL); - assert(mesh->avahi_server != NULL); - assert(mesh->avahi_browser != NULL); - assert(mesh->discovery_threadstarted == true); - assert(mesh->avahi_servicetype != NULL); + - // Shut down - avahi_simple_poll_quit(mesh->avahi_poll); ++ // Shut down ++ if(mesh->avahi_poll) ++ { ++ avahi_simple_poll_quit(mesh->avahi_poll); ++ } + + // Wait for the discovery thread to finish - pthread_join(mesh->discovery_thread, NULL); ++ if(mesh->discovery_threadstarted == true) ++ { ++ pthread_join(mesh->discovery_thread, NULL); ++ mesh->discovery_threadstarted = false; ++ } + + // Clean up resources - avahi_s_service_browser_free(mesh->avahi_browser); - mesh->avahi_browser = NULL; ++ if(mesh->avahi_browser != NULL) ++ { ++ avahi_s_service_browser_free(mesh->avahi_browser); ++ mesh->avahi_browser = NULL; ++ } + - avahi_server_free(mesh->avahi_server); - mesh->avahi_server = NULL; ++ if(mesh->avahi_server != NULL) ++ { ++ avahi_server_free(mesh->avahi_server); ++ mesh->avahi_server = NULL; ++ } + - avahi_simple_poll_free(mesh->avahi_poll); - mesh->avahi_poll = NULL; ++ if(mesh->avahi_poll != NULL) ++ { ++ avahi_simple_poll_free(mesh->avahi_poll); ++ mesh->avahi_poll = NULL; ++ } + - free(mesh->avahi_servicetype); - mesh->avahi_servicetype = NULL; ++ if(mesh->avahi_servicetype != NULL) ++ { ++ free(mesh->avahi_servicetype); ++ mesh->avahi_servicetype = NULL; ++ } + } diff --combined src/meshlink++.h index 8f852d9f,2bc1f8b0..9500852b --- a/src/meshlink++.h +++ b/src/meshlink++.h @@@ -21,7 -21,6 +21,7 @@@ #define MESHLINKPP_H #include +#include // for 'placement new' namespace meshlink { class mesh; @@@ -87,30 -86,9 +87,30 @@@ /// A class describing a MeshLink mesh. class mesh: public meshlink_handle_t { - public: - // TODO: delete constructor, add a destructor. - + public: + mesh() {} + + 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. @@@ -118,9 -96,6 +118,9 @@@ * @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); } @@@ -132,6 -107,46 +132,6 @@@ 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 @@@ -344,29 -359,6 +344,29 @@@ 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. @@@ -381,13 -373,12 +381,14 @@@ * * @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. * * @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, const char* appname) { - return (mesh *)meshlink_open(confbase, name, appname); + template - static MESH* open(const char *confbase, const char *name) { - void* mp = (void *)meshlink_open_with_size(confbase, name, sizeof(MESH)); ++ static MESH* open(const char *confbase, const char *name, const char* appname) { ++ void* mp = (void *)meshlink_open_with_size(confbase, name, appname, sizeof(MESH)); + return new (mp) MESH; } /// Close the MeshLink handle. @@@ -402,7 -393,6 +403,7 @@@ static const char *strerror(errno_t err = meshlink_errno) { return meshlink_strerror(err); } -}; + +} #endif // MESHLINKPP_H diff --combined src/meshlink.c index 4fa3a580,33af6c36..d5ea5418 --- a/src/meshlink.c +++ b/src/meshlink.c @@@ -21,6 -21,8 +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; @@@ -39,6 -41,7 +41,7 @@@ #include "utils.h" #include "xalloc.h" #include "ed25519/sha512.h" + #include "discovery.h" #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 @@@ -347,7 -350,12 +350,7 @@@ static bool try_bind(int port) } 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]; @@@ -360,6 -368,7 +363,6 @@@ fprintf(f, "Port = %d\n", port); fclose(f); - fprintf(stderr, "MeshLink will instead listen on port %d.\n", port); return port; } } @@@ -734,13 -743,7 +737,12 @@@ static bool meshlink_setup(meshlink_han return true; } - - meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { - return meshlink_open_with_size(confbase, name, sizeof(meshlink_handle_t) ); + meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char* appname) { ++ return meshlink_open_with_size(confbase, name, appname, sizeof(meshlink_handle_t)); +} + ++meshlink_handle_t *meshlink_open_with_size(const char *confbase, const char *name, const char* appname, size_t size) { + - meshlink_handle_t *meshlink_open_with_size(const char *confbase, const char *name, size_t size) { // Validate arguments provided by the application bool usingname = false; @@@ -750,6 -753,12 +752,12 @@@ return NULL; } + if(!appname || !*appname) { + fprintf(stderr, "No appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + if(!name || !*name) { fprintf(stderr, "No name given!\n"); //return NULL; @@@ -763,9 -772,11 +771,10 @@@ } else { usingname = true;} } - meshlink_handle_t *mesh = xzalloc(sizeof *mesh); + meshlink_handle_t *mesh = xzalloc(size); mesh->confbase = xstrdup(confbase); + mesh->appname = xstrdup(appname); if (usingname) mesh->name = xstrdup(name); - pthread_mutex_init ( &(mesh->outpacketqueue_mutex), NULL); pthread_mutex_init ( &(mesh->nodes_mutex), NULL); mesh->threadstarted = false; event_loop_init(&mesh->loop); @@@ -826,9 -837,7 +835,9 @@@ static void *meshlink_main_loop(void *a try_outgoing_connections(mesh); + fprintf(stderr, "Starting main_loop...\n"); main_loop(mesh); + fprintf(stderr, "main_loop returned.\n"); return NULL; } @@@ -859,35 -868,32 +868,42 @@@ bool meshlink_start(meshlink_handle_t * mesh->threadstarted=true; + // Start discovery + if(!discovery_start(mesh)) + return false; + return true; } void meshlink_stop(meshlink_handle_t *mesh) { ++ if(!mesh) { meshlink_errno = MESHLINK_EINVAL; return; } - listen_socket_t *s = &mesh->listen_socket[0]; + // Stop discovery + discovery_stop(mesh); - // Shut down the listening sockets to signal the main thread to shut down + // Shut down a listening socket 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); - } ++ listen_socket_t *s = &mesh->listen_socket[0]; + shutdown(s->tcp.fd, SHUT_RDWR); // Wait for the main thread to finish pthread_join(mesh->thread, NULL); + 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(DEBUG_ALWAYS, LOG_ERR, "Could not repair listenen socket!"); + else + io_add(&mesh->loop, &s->tcp, handle_new_meta_connection, s, s->tcp.fd, IO_READ); } void meshlink_close(meshlink_handle_t *mesh) { @@@ -962,15 -968,19 +978,15 @@@ bool meshlink_send(meshlink_handle_t *m return false; } - /* If there is no outgoing list yet, create one. */ - - if(!mesh->outpacketqueue) - mesh->outpacketqueue = list_alloc(NULL); - //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); + return false; + } //notify event loop signal_trigger(&(mesh->loop),&(mesh->datafromapp)); @@@ -981,9 -991,10 +997,9 @@@ void meshlink_send_from_queue(event_loo 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) + return; if (sizeof(meshlink_packethdr_t) + p->len > MAXSIZE) { //log something @@@ -1392,7 -1403,7 +1408,7 @@@ bool meshlink_join(meshlink_handle_t *m } if(!port) - port = "655"; + goto invalid; if(!b64decode(slash, mesh->hash, 18) || !b64decode(slash + 24, mesh->cookie, 18)) goto invalid; @@@ -1653,6 -1664,47 +1669,47 @@@ void meshlink_whitelist(meshlink_handle return; } + /* Hint that a hostname may be found at an address + * See header file for detailed comment. + */ + extern void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, struct sockaddr *addr) { + if(!mesh || !node || !addr) + return; + + char *addr_str = malloc(MAX_ADDRESS_LENGTH*sizeof(char)); + memset(addr_str, 0, MAX_ADDRESS_LENGTH*sizeof(char)); + + char *port_str = malloc(MAX_PORT_LENGTH*sizeof(char)); + memset(port_str, 0, MAX_PORT_LENGTH*sizeof(char)); + + // extra byte for a space, and one to make sure string is null-terminated + int full_addr_len = MAX_ADDRESS_LENGTH + MAX_PORT_LENGTH + 2; + + char *full_addr_str = malloc(full_addr_len*sizeof(char)); + memset(full_addr_str, 0, full_addr_len*sizeof(char)); + + // get address and port number + if(!get_ip_str(addr, addr_str, MAX_ADDRESS_LENGTH)) + goto fail; + if(!get_port_str(addr, port_str, MAX_ADDRESS_LENGTH)) + goto fail; + + // append_config_file expects an address, a space, and then a port number + strcat(full_addr_str, addr_str); + strcat(full_addr_str, " "); + strcat(full_addr_str, port_str); + + append_config_file(mesh, node->name, "Address", full_addr_str); + + fail: + done: + free(addr_str); + free(port_str); + free(full_addr_str); + + // @TODO do we want to fire off a connection attempt right away? + } + static void __attribute__((constructor)) meshlink_init(void) { crypto_init(); } diff --combined src/meshlink.h index 603dbbd2,37fe5540..96fb8d6e --- a/src/meshlink.h +++ b/src/meshlink.h @@@ -25,6 -25,6 +25,13 @@@ #include #include ++#if defined(_WIN32) ++#include ++#else ++#include ++#include ++#endif ++ #ifdef __cplusplus extern "C" { #endif @@@ -32,6 -32,9 +39,9 @@@ /// The length in bytes of a signature made with meshlink_sign() #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; @@@ -111,15 -114,14 +121,17 @@@ extern const char *meshlink_strerror(me * 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. * * @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); +/// 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, size_t size); ++extern meshlink_handle_t *meshlink_open_with_size(const char *confbase, const char *name, const char* appname, 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. @@@ -565,6 -567,20 +577,20 @@@ extern void meshlink_channel_close(mesh */ 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, struct sockaddr *addr); + #ifdef __cplusplus } #endif diff --combined src/meshlink_internal.h index c9c1eeaa,8ac270be..6b47481f --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@@ -26,7 -26,6 +26,7 @@@ #include "hash.h" #include "logger.h" #include "meshlink.h" +#include "meshlink_queue.h" #include "sockaddr.h" #include "sptps.h" @@@ -34,6 -33,11 +34,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; @@@ -63,6 -67,8 +68,8 @@@ struct meshlink_handle char *confbase; + char *appname; + meshlink_receive_cb_t receive_cb; meshlink_node_status_cb_t node_status_cb; meshlink_log_cb_t log_cb; @@@ -86,7 -92,7 +93,7 @@@ 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; @@@ -127,6 -133,14 +134,14 @@@ 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.