]> git.meshlink.io Git - meshlink/commitdiff
Provide a way to open MeshLink in its own network namespace.
authorGuus Sliepen <guus@meshlink.io>
Sat, 26 Jan 2019 20:38:53 +0000 (21:38 +0100)
committerGuus Sliepen <guus@meshlink.io>
Thu, 31 Jan 2019 21:24:15 +0000 (22:24 +0100)
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
src/devtools.h
src/meshlink.c
src/meshlink.h
src/meshlink_internal.h

index 3f2c0a4942797ff24d931ffccdec65fcdac6d043..6d22516d145c16b04e095610977f57bf4c7d55a3 100644 (file)
@@ -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;
+}
index e74d41376b7a0644b4cba2e235fc08521b66ef20..92862632b9a222cfe8f7e8ccacde325590736857 100644 (file)
@@ -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
index 7994cf3edb295686a0b7955ea6c4faa8b7784323..520176232d6f05beb68fa2eeb11caa60116e9b61 100644 (file)
@@ -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, &params) == 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(&params);
+}
+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));
index 74ebae5d71e4eb5771312f57bbe5a08e663a2130..968d344283b96c491c8fe3759e9c55d34a906ec0 100644 (file)
@@ -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.
index 92774c49baf3c9481d4aac62273256156c77d629..f576eb1290a1178c14b02fddc273dd3f601a013c 100644 (file)
@@ -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.