From 6054182c9e208fd0b3f5c6c90f54cb5af75d3f5c Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sat, 26 Jan 2019 21:38:53 +0100 Subject: [PATCH] Provide a way to open MeshLink in its own network namespace. This causes all sockets from MeshLink to be opened in the given network namespace, without affecting the application's namespace. Note that since callback functions run inside MeshLink's own thread, the callback functions inherit MeshLink's network namespace. --- src/devtools.c | 17 ++++ src/devtools.h | 17 ++++ src/meshlink.c | 187 +++++++++++++++++++++++++++++++++++----- src/meshlink.h | 52 +++++++++++ src/meshlink_internal.h | 11 +++ 5 files changed, 262 insertions(+), 22 deletions(-) diff --git a/src/devtools.c b/src/devtools.c index 3f2c0a49..6d22516d 100644 --- a/src/devtools.c +++ b/src/devtools.c @@ -279,3 +279,20 @@ void devtool_get_node_status(meshlink_handle_t *mesh, meshlink_node_t *node, dev pthread_mutex_unlock(&mesh->mesh_mutex); } + +meshlink_handle_t *devtool_open_in_netns(const char *confbase, const char *name, const char *appname, dev_class_t devclass, int netns) { + meshlink_open_params_t *params = meshlink_open_params_init(confbase, name, appname, devclass); + params->netns = dup(netns); + meshlink_handle_t *handle; + + if(params->netns == -1) { + handle = NULL; + meshlink_errno = MESHLINK_EINVAL; + } else { + handle = meshlink_open_ex(params); + } + + meshlink_open_params_free(params); + + return handle; +} diff --git a/src/devtools.h b/src/devtools.h index e74d4137..92862632 100644 --- a/src/devtools.h +++ b/src/devtools.h @@ -120,4 +120,21 @@ struct devtool_node_status { */ extern void devtool_get_node_status(meshlink_handle_t *mesh, meshlink_node_t *node, devtool_node_status_t *status); +/// Open a MeshLink instance in a given network namespace. +/** This function opens MeshLink in the given network namespace. + * + * @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. + * @param netns A filedescriptor that represents the network namespace. + * + * @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 *devtool_open_in_netns(const char *confbase, const char *name, const char *appname, dev_class_t devclass, int netns); + #endif diff --git a/src/meshlink.c b/src/meshlink.c index 7994cf3e..52017623 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -203,10 +203,46 @@ static void set_timeout(int sock, int timeout) { setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); } +struct socket_in_netns_params { + int domain; + int type; + int protocol; + int netns; + int fd; +}; + +static void *socket_in_netns_thread(void *arg) { + struct socket_in_netns_params *params = arg; + + if(setns(params->netns, CLONE_NEWNET) == -1) { + meshlink_errno = MESHLINK_EINVAL; + } else { + params->fd = socket(params->domain, params->type, params->protocol); + } + + return NULL; +} + +static int socket_in_netns(int domain, int type, int protocol, int netns) { + if(netns == -1) { + return socket(domain, type, protocol); + } + + struct socket_in_netns_params params = {domain, type, protocol, netns, -1}; + + pthread_t thr; + + if(pthread_create(&thr, NULL, socket_in_netns_thread, ¶ms) == 0) { + pthread_join(thr, NULL); + } + + return params.fd; +} + // Find out what local address a socket would use if we connect to the given address. // We do this using connect() on a UDP socket, so the kernel has to resolve the address // of both endpoints, but this will actually not send any UDP packet. -static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen) { +static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen, int netns) { struct addrinfo *rai = NULL; const struct addrinfo hint = { .ai_family = AF_UNSPEC, @@ -218,7 +254,7 @@ static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen) { return false; } - int sock = socket(rai->ai_family, rai->ai_socktype, rai->ai_protocol); + int sock = socket_in_netns(rai->ai_family, rai->ai_socktype, rai->ai_protocol, netns); if(sock == -1) { freeaddrinfo(rai); @@ -267,7 +303,7 @@ char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int fami continue; } - int s = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); + int s = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns); if(s >= 0) { set_timeout(s, 5000); @@ -329,9 +365,9 @@ char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int family) bool success = false; if(family == AF_INET) { - success = getlocaladdrname("93.184.216.34", localaddr, sizeof(localaddr)); + success = getlocaladdrname("93.184.216.34", localaddr, sizeof(localaddr), mesh->netns); } else if(family == AF_INET6) { - success = getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", localaddr, sizeof(localaddr)); + success = getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", localaddr, sizeof(localaddr), mesh->netns); } if(!success) { @@ -1070,14 +1106,14 @@ static void add_local_addresses(meshlink_handle_t *mesh) { // IPv4 example.org - if(getlocaladdrname("93.184.216.34", host, sizeof(host))) { + if(getlocaladdrname("93.184.216.34", host, sizeof(host), mesh->netns)) { snprintf(entry, sizeof(entry), "%s %s", host, mesh->myport); append_config_file(mesh, mesh->name, "Address", entry); } // IPv6 example.org - if(getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", host, sizeof(host))) { + if(getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", host, sizeof(host), mesh->netns)) { snprintf(entry, sizeof(entry), "%s %s", host, mesh->myport); append_config_file(mesh, mesh->name, "Address", entry); } @@ -1133,12 +1169,19 @@ static bool meshlink_setup(meshlink_handle_t *mesh) { return true; } -meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { - // Validate arguments provided by the application - bool usingname = false; +static void *setup_network_in_netns_thread(void *arg) { + meshlink_handle_t *mesh = arg; - logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n"); + if(setns(mesh->netns, CLONE_NEWNET) != 0) { + return NULL; + } + + bool success = setup_network(mesh); + add_local_addresses(mesh); + return success ? arg : NULL; +} +meshlink_open_params_t *meshlink_open_params_init(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { if(!confbase || !*confbase) { logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); meshlink_errno = MESHLINK_EINVAL; @@ -1161,31 +1204,109 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c logger(NULL, MESHLINK_ERROR, "No name given!\n"); //return NULL; } else { //check name only if there is a name != NULL - if(!check_id(name)) { logger(NULL, MESHLINK_ERROR, "Invalid name given!\n"); meshlink_errno = MESHLINK_EINVAL; return NULL; + } + } + + if((int)devclass < 0 || devclass > _DEV_CLASS_MAX) { + logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + meshlink_open_params_t *params = xzalloc(sizeof * params); + + params->confbase = xstrdup(confbase); + params->name = xstrdup(name); + params->appname = xstrdup(appname); + params->devclass = devclass; + params->netns = -1; + + return params; +} + +void meshlink_open_params_free(meshlink_open_params_t *params) { + if(!params) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + free(params->confbase); + free(params->name); + free(params->appname); + + free(params); +} + +meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) { + /* Create a temporary struct on the stack, to avoid allocating and freeing one. */ + meshlink_open_params_t params = {NULL}; + + params.confbase = (char *)confbase; + params.name = (char *)name; + params.appname = (char *)appname; + params.devclass = devclass; + params.netns = -1; + + return meshlink_open_ex(¶ms); +} +meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) { + // Validate arguments provided by the application + bool usingname = false; + + logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n"); + + if(!params->confbase || !*params->confbase) { + logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!params->appname || !*params->appname) { + logger(NULL, MESHLINK_ERROR, "No appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(strchr(params->appname, ' ')) { + logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; + } + + if(!params->name || !*params->name) { + logger(NULL, MESHLINK_ERROR, "No name given!\n"); + //return NULL; + } else { //check name only if there is a name != NULL + + if(!check_id(params->name)) { + logger(NULL, MESHLINK_ERROR, "Invalid name given!\n"); + meshlink_errno = MESHLINK_EINVAL; + return NULL; } else { usingname = true; } } - if((int)devclass < 0 || devclass > _DEV_CLASS_MAX) { + if((int)params->devclass < 0 || params->devclass > _DEV_CLASS_MAX) { logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n"); meshlink_errno = MESHLINK_EINVAL; return NULL; } meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t)); - mesh->confbase = xstrdup(confbase); - mesh->appname = xstrdup(appname); - mesh->devclass = devclass; + mesh->confbase = xstrdup(params->confbase); + mesh->appname = xstrdup(params->appname); + mesh->devclass = params->devclass; mesh->discovery = true; mesh->invitation_timeout = 604800; // 1 week + mesh->netns = params->netns; if(usingname) { - mesh->name = xstrdup(name); + mesh->name = xstrdup(params->name); } // initialize mutex @@ -1203,7 +1324,7 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c // Check whether meshlink.conf already exists char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", confbase); + snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", params->confbase); if(access(filename, R_OK)) { if(errno == ENOENT) { @@ -1268,14 +1389,26 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c // Setup up everything // TODO: we should not open listening sockets yet - if(!setup_network(mesh)) { + bool success = false; + + if(mesh->netns != -1) { + pthread_t thr; + + if(pthread_create(&thr, NULL, setup_network_in_netns_thread, mesh) == 0) { + void *retval = NULL; + success = pthread_join(thr, &retval) == 0 && retval; + } + } else { + success = setup_network(mesh); + add_local_addresses(mesh); + } + + if(!success) { meshlink_close(mesh); meshlink_errno = MESHLINK_ENETWORK; return NULL; } - add_local_addresses(mesh); - idle_set(&mesh->loop, idle, mesh); logger(NULL, MESHLINK_DEBUG, "meshlink_open returning\n"); @@ -1285,6 +1418,12 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c static void *meshlink_main_loop(void *arg) { meshlink_handle_t *mesh = arg; + if(mesh->netns != -1) { + if(setns(mesh->netns, CLONE_NEWNET) != 0) { + return NULL; + } + } + pthread_mutex_lock(&(mesh->mesh_mutex)); try_outgoing_connections(mesh); @@ -1452,6 +1591,10 @@ void meshlink_close(meshlink_handle_t *mesh) { ecdsa_free(mesh->invitation_key); + if(mesh->netns != -1) { + close(mesh->netns); + } + free(mesh->name); free(mesh->appname); free(mesh->confbase); @@ -2245,7 +2388,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { if(ai) { for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { - mesh->sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); + mesh->sock = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns); if(mesh->sock == -1) { logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno)); diff --git a/src/meshlink.h b/src/meshlink.h index 74ebae5d..968d3442 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -51,6 +51,9 @@ typedef struct meshlink_node meshlink_node_t; /// A handle for a MeshLink channel. typedef struct meshlink_channel meshlink_channel_t; +/// A struct containing all parameters used for opening a mesh. +typedef struct meshlink_open_params meshlink_open_params_t; + /// Code of most recent error encountered. typedef enum { MESHLINK_OK, ///< Everything is fine @@ -116,6 +119,10 @@ struct meshlink_channel { void *priv; ///< Private pointer which may be set freely by the application, and is never used or modified by MeshLink. }; +struct meshlink_open_params { + /* This is an opaque struct, all parameters must be set using the corresponding meshlink_open_params_*() functions. */ +}; + #endif // MESHLINK_INTERNAL_H /// Get the text for the given MeshLink error code. @@ -129,6 +136,51 @@ struct meshlink_channel { */ extern const char *meshlink_strerror(meshlink_errno_t err); +/// Create a new meshlink_open_params_t struct. +/** This function allocates and initializes a new meshlink_open_params_t struct that can be passed to meshlink_open_ex(). + * The resulting struct may be reused for multiple calls to meshlink_open_ex(). + * After the last use, the application must free this struct using meshlink_open_params_free(). + * + * @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 A pointer to a meshlink_open_params_t which can be passed to meshlink_open_ex(), or NULL in case of an error. + * The pointer is valid until meshlink_open_params_free() is called. + */ +extern meshlink_open_params_t *meshlink_open_params_init(const char *confbase, const char *name, const char *appname, dev_class_t devclass); + +/// Free a meshlink_open_params_t struct. +/** This function frees a meshlink_open_params_t struct and all resources associated with it. + * + * @param params A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init(). + */ +extern void meshlink_open_params_free(meshlink_open_params_t *params); + +/// Open or create a MeshLink instance. +/** This function opens or creates a MeshLink instance. + * All parameters needed by MeshLink are passed via a meshlink_open_params_t struct, + * which must have been initialized earlier by the application. + * + * 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 params A pointer to a meshlink_open_params_t which must be filled in by the application. + * After the function returns, the application is free to reuse or free @a params @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_ex(const meshlink_open_params_t *params); + /// 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. diff --git a/src/meshlink_internal.h b/src/meshlink_internal.h index 92774c49..f576eb12 100644 --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@ -57,6 +57,15 @@ typedef enum proxytype_t { PROXY_HTTP, } proxytype_t; +struct meshlink_open_params { + char *confbase; + char *appname; + char *name; + dev_class_t devclass; + + int netns; +}; + /// A handle for an instance of MeshLink. struct meshlink_handle { char *name; @@ -145,6 +154,8 @@ struct meshlink_handle { struct CattaSimplePoll *catta_poll; struct CattaSEntryGroup *catta_group; char *catta_servicetype; + + int netns; }; /// A handle for a MeshLink node. -- 2.39.2