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");
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;
#include <strings.h>
#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;
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");
printf("Message sent to '%s'.\n", destination->name);
}
+
int main(int argc, char *argv[]) {
const char *confbase = ".chat";
const char *nick = NULL;
if(argc > 2)
nick = argv[2];
- ChatMesh* mesh = meshlink::open<ChatMesh>(confbase, nick);
- meshlink::mesh *mesh = meshlink::open(confbase, nick, "chatpp");
++ ChatMesh* mesh = meshlink::open<ChatMesh>(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;
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() {
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 {
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]) {
--- /dev/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);
+
+ #include "meshlink_internal.h"
+ #include "discovery.h"
+ #include "sockaddr.h"
+
+ #include <pthread.h>
+
+ #include <avahi-core/core.h>
+ #include <avahi-core/lookup.h>
+ #include <avahi-core/publish.h>
+ #include <avahi-common/simple-watch.h>
+ #include <avahi-common/malloc.h>
+ #include <avahi-common/alternative.h>
+ #include <avahi-common/error.h>
+
+ #include <netinet/in.h>
+
+ #include <uuid/uuid.h>
+
+ #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);
- // Shut down
- avahi_simple_poll_quit(mesh->avahi_poll);
+
- pthread_join(mesh->discovery_thread, NULL);
++ // Shut down
++ if(mesh->avahi_poll)
++ {
++ avahi_simple_poll_quit(mesh->avahi_poll);
++ }
+
+ // Wait for the discovery thread to finish
- avahi_s_service_browser_free(mesh->avahi_browser);
- mesh->avahi_browser = NULL;
++ if(mesh->discovery_threadstarted == true)
++ {
++ pthread_join(mesh->discovery_thread, NULL);
++ mesh->discovery_threadstarted = false;
++ }
+
+ // Clean up resources
- avahi_server_free(mesh->avahi_server);
- mesh->avahi_server = NULL;
++ if(mesh->avahi_browser != NULL)
++ {
++ avahi_s_service_browser_free(mesh->avahi_browser);
++ mesh->avahi_browser = NULL;
++ }
+
- avahi_simple_poll_free(mesh->avahi_poll);
- mesh->avahi_poll = NULL;
++ if(mesh->avahi_server != NULL)
++ {
++ avahi_server_free(mesh->avahi_server);
++ mesh->avahi_server = NULL;
++ }
+
- free(mesh->avahi_servicetype);
- mesh->avahi_servicetype = 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;
++ }
+ }
#define MESHLINKPP_H
#include <meshlink.h>
+#include <new> // for 'placement new'
namespace meshlink {
class mesh;
/// 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.
* @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);
}
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
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<mesh*>(handle);
+ that->receive(static_cast<node*>(source), data, length);
+ }
+
+ static void node_status_trampoline(meshlink_handle_t* handle, meshlink_node_t* peer, bool reachable)
+ {
+ mesh* that = static_cast<mesh*>(handle);
+ that->node_status(static_cast<node*>(peer), reachable);
+ }
+
+ static void log_trampoline(meshlink_handle_t* handle, log_level_t level, const char* message)
+ {
+ mesh* that = static_cast<mesh*>(handle);
+ that->log(level, message);
+ }
};
/// Initialize MeshLink's configuration directory.
*
* @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<class MESH>
- 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.
static const char *strerror(errno_t err = meshlink_errno) {
return meshlink_strerror(err);
}
-};
+
+}
#endif // MESHLINKPP_H
#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;
#include "utils.h"
#include "xalloc.h"
#include "ed25519/sha512.h"
+ #include "discovery.h"
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
}
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];
fprintf(f, "Port = %d\n", port);
fclose(f);
- fprintf(stderr, "MeshLink will instead listen on port %d.\n", port);
return port;
}
}
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;
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;
} 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);
try_outgoing_connections(mesh);
+ fprintf(stderr, "Starting main_loop...\n");
main_loop(mesh);
+ fprintf(stderr, "main_loop returned.\n");
return NULL;
}
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) {
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));
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
}
if(!port)
- port = "655";
+ goto invalid;
if(!b64decode(slash, mesh->hash, 18) || !b64decode(slash + 24, mesh->cookie, 18))
goto invalid;
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();
}
#include <stddef.h>
#include <unistd.h>
++#if defined(_WIN32)
++#include <Winsock2.h>
++#else
++#include <sys/types.h>
++#include <sys/socket.h>
++#endif
++
#ifdef __cplusplus
extern "C" {
#endif
/// 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;
* 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);
- extern meshlink_handle_t *meshlink_open_with_size(const char *confbase, const char *name, size_t size);
+/// 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, 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.
*/
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
#include "hash.h"
#include "logger.h"
#include "meshlink.h"
+#include "meshlink_queue.h"
#include "sockaddr.h"
#include "sptps.h"
#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;
char *confbase;
+ char *appname;
+
meshlink_receive_cb_t receive_cb;
meshlink_node_status_cb_t node_status_cb;
meshlink_log_cb_t log_cb;
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;
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.