jail: make use of per-container netifd via ubus
authorDaniel Golle <daniel@makrotopia.org>
Tue, 12 Oct 2021 00:30:38 +0000 (01:30 +0100)
committerDaniel Golle <daniel@makrotopia.org>
Tue, 12 Oct 2021 23:36:00 +0000 (00:36 +0100)
Make ujail expose 'network_reload' function on ubus so we can
reconfigure network interfaces inside containers.
Automatically call that function on network config changes by
subscribing to ubus 'service' object and waiting for 'config.change'
events concerning the network configuration.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
jail/jail.c
jail/jail.h
jail/netifd.c

index ff54d970b6953a1539910bda1327007c43641f04..3f42bcbd96f3568aed8882d6d20aa8719afdaf0f 100644 (file)
@@ -1293,7 +1293,7 @@ static void post_start_hook(void)
        exit(EXIT_FAILURE);
 }
 
-static int ns_open_pid(const char *nstype, const pid_t target_ns)
+int ns_open_pid(const char *nstype, const pid_t target_ns)
 {
        char pid_pid_path[PATH_MAX];
 
@@ -1302,26 +1302,6 @@ static int ns_open_pid(const char *nstype, const pid_t target_ns)
        return open(pid_pid_path, O_RDONLY);
 }
 
-static void netns_updown(pid_t pid, bool start)
-{
-       static struct blob_buf req;
-       uint32_t id;
-
-       if (!parent_ctx)
-               return;
-
-       blob_buf_init(&req, 0);
-       blobmsg_add_string(&req, "jail", opts.name);
-       blobmsg_add_u32(&req, "pid", pid);
-       blobmsg_add_u8(&req, "start", start);
-
-       if (ubus_lookup_id(parent_ctx, "network", &id) ||
-           ubus_invoke(parent_ctx, id, "netns_updown", req.head, NULL, NULL, 3000))
-               INFO("ubus request failed\n");
-
-       blob_buf_free(&req);
-}
-
 static int parseOCIenvarray(struct blob_attr *msg, char ***envp)
 {
        struct blob_attr *cur;
@@ -3027,11 +3007,8 @@ static void post_main(struct uloop_timeout *t)
                        }
                }
 
-               if (opts.namespace & CLONE_NEWNET) {
+               if (opts.namespace & CLONE_NEWNET)
                        jail_network_start(parent_ctx, opts.name, jail_process.pid);
-                       netns_fd = ns_open_pid("net", jail_process.pid);
-                       netns_updown(jail_process.pid, true);
-               }
 
                if (jail_writepid(jail_process.pid)) {
                        ERROR("failed to write pidfile: %m\n");
@@ -3097,7 +3074,6 @@ static void post_poststop(void);
 static void poststop(void) {
        if (opts.namespace & CLONE_NEWNET) {
                setns(netns_fd, CLONE_NEWNET);
-               netns_updown(getpid(), false);
                jail_network_stop();
                close(netns_fd);
        }
index 5739d3d269020fff849eb2be4119008bfa7cc3ee..158d73b7299c178293ddf82e6641892ad1ddbfbe 100644 (file)
@@ -14,5 +14,6 @@
 #define _JAIL_JAIL_H_
 
 int mount_bind(const char *root, const char *path, int readonly, int error);
+int ns_open_pid(const char *nstype, const pid_t target_ns);
 
 #endif
index bedf2f4257167de9c1051b50f91c21ec18d4c314..f10899353edbdab7637b30b5f2e530d18b0151f5 100644 (file)
@@ -38,6 +38,7 @@
 
 #include "netifd.h"
 #include "log.h"
+#include "jail.h"
 
 #define INOTIFY_SZ (sizeof(struct inotify_event) + PATH_MAX + 1)
 
@@ -49,18 +50,34 @@ static char *jail_name, *ubus_sock_path, *ubus_sock_dir, *uci_config_network = N
 
 static char *inotify_buffer;
 static struct uloop_fd fd_inotify_read;
-struct ubus_context *ctx;
 static struct passwd *ubus_pw;
 static pid_t ns_pid;
 
+static struct ubus_context *ctx = NULL;
+static struct ubus_context *netifd_ubus_ctx = NULL;
+
+static struct ubus_subscriber config_watch_subscribe;
+
 /* generate /etc/config/network for jail'ed netifd */
-static int gen_jail_uci_network(const char *jailname, FILE *f)
+static int gen_jail_uci_network(void)
 {
        struct uci_context *ctx = uci_alloc_context();
        struct uci_package *pkg = NULL;
        struct uci_element *e, *t;
        bool has_loopback = false;
        int ret = 0;
+       FILE *ucinetf;
+
+       /* if no network configuration is active just return */
+       if (!uci_config_network)
+               goto uci_out;
+
+       /* open output uci network config file */
+       ucinetf = fopen(uci_config_network, "w");
+       if (!ucinetf) {
+               ret = errno;
+               goto uci_out;
+       }
 
        /* load network uci package */
        if (uci_load(ctx, uci_net, &pkg) != UCI_OK) {
@@ -69,7 +86,7 @@ static int gen_jail_uci_network(const char *jailname, FILE *f)
                fprintf(stderr, "unable to load configuration (%s)\n", err);
                free(err);
                ret = EIO;
-               goto uci_out;
+               goto ucinetf_out;
        }
 
        /* remove all sections which don't match jail */
@@ -79,7 +96,7 @@ static int gen_jail_uci_network(const char *jailname, FILE *f)
                struct uci_ptr ptr = { .p = pkg, .s = s };
 
                /* keep match, but remove 'jail' option and rename 'jail_ifname' */
-               if (o && o->type == UCI_TYPE_STRING && !strcmp(o->v.string, jailname)) {
+               if (o && o->type == UCI_TYPE_STRING && !strcmp(o->v.string, jail_name)) {
                        ptr.o = o;
                        struct uci_option *jio = uci_lookup_option(ctx, s, "jail_device");
                        if (!jio)
@@ -131,7 +148,10 @@ static int gen_jail_uci_network(const char *jailname, FILE *f)
                uci_set(ctx, &ptr4);
        }
 
-       ret = uci_export(ctx, f, pkg, false);
+       ret = uci_export(ctx, ucinetf, pkg, false);
+
+ucinetf_out:
+       fclose(ucinetf);
 
 uci_out:
        uci_free_context(ctx);
@@ -169,21 +189,23 @@ static void run_ubusd(struct uloop_timeout *t)
        blob_buf_free(&req);
 }
 
-
 static void run_netifd(struct uloop_timeout *t)
 {
        static struct blob_buf req;
        void *ins, *in, *cmd, *jail, *setns, *setnso, *namespaces, *mount;
-       char *resolvconf_dir, *resolvconf, *ucinetmount;
-       char uci_dir[] = "/tmp/ujail-uci-XXXXXX";
+       char *resolvconf_dir, *resolvconf, *ucimount;
+       char uci_dir[] = "/var/containers/ujail-uci-XXXXXX";
 
-       FILE *ucinetf;
        uint32_t id;
        bool running = false;
 
        uloop_fd_delete(&fd_inotify_read);
        close(fd_inotify_read.fd);
 
+       netifd_ubus_ctx = ubus_connect(ubus_sock_path);
+       if (!netifd_ubus_ctx)
+               return;
+
        if (asprintf(&resolvconf_dir, "/tmp/resolv.conf-%s.d", jail_name) == -1)
                return;
 
@@ -196,18 +218,11 @@ static void run_netifd(struct uloop_timeout *t)
        if (asprintf(&uci_config_network, "%s/network", uci_dir) == -1)
                goto netifd_out_ucidir;
 
-       if (asprintf(&ucinetmount, "%s:/etc/config/network", uci_config_network) == -1)
+       if (asprintf(&ucimount, "%s:/etc/config", uci_dir) == -1)
                goto netifd_out_ucinetconf;
 
-       ucinetf = fopen(uci_config_network, "w");
-       if (!ucinetf)
-               goto netifd_out_ucinetmount;
-
-       if (gen_jail_uci_network(jail_name, ucinetf)) {
-               fclose(ucinetf);
-               goto netifd_out_ucinetmount;
-       }
-       fclose(ucinetf);
+       if (gen_jail_uci_network())
+               goto netifd_out_ucimount;
 
        blob_buf_init(&req, 0);
        blobmsg_add_string(&req, "name", jail_name);
@@ -238,7 +253,7 @@ static void run_netifd(struct uloop_timeout *t)
        mount = blobmsg_open_table(&req, "mount");
        blobmsg_add_string(&req, ubus_sock_dir, "1");
        blobmsg_add_string(&req, resolvconf_dir, "1");
-       blobmsg_add_string(&req, ucinetmount, "0");
+       blobmsg_add_string(&req, ucimount, "0");
        blobmsg_add_string(&req, "/etc/hotplug.d", "0");
        blobmsg_add_string(&req, "/lib/functions.sh", "0");
        blobmsg_add_string(&req, "/lib/netifd", "0");
@@ -267,13 +282,15 @@ static void run_netifd(struct uloop_timeout *t)
        if (!ubus_lookup_id(ctx, "container", &id))
                running = !ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000);
 
-       blob_buf_free(&req);
-netifd_out_ucinetmount:
-       free(ucinetmount);
-netifd_out_ucinetconf:
        if (!running)
+               blob_buf_free(&req);
+netifd_out_ucimount:
+       free(ucimount);
+netifd_out_ucinetconf:
+       if (!running) {
                unlink(uci_config_network);
-       free(uci_config_network);
+               free(uci_config_network);
+       }
 netifd_out_ucidir:
        if (!running)
                rmdir(uci_dir);
@@ -285,26 +302,6 @@ netifd_out_resolvconf_dir:
        uloop_end();
 }
 
-static int kill_jail_instance(char *instance)
-{
-       static struct blob_buf req;
-       uint32_t id;
-       int ret = 0;
-
-       blob_buf_init(&req, 0);
-       blobmsg_add_string(&req, "name", jail_name);
-       blobmsg_add_string(&req, "instance", instance);
-
-       if (ubus_lookup_id(ctx, "container", &id) ||
-               ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000)) {
-               ret = EIO;
-       }
-
-       blob_buf_free(&req);
-
-       return ret;
-}
-
 static struct uloop_timeout netifd_start_timeout = { .cb = run_netifd, };
 
 static void inotify_read_handler(struct uloop_fd *u, unsigned int events)
@@ -333,12 +330,96 @@ static void inotify_read_handler(struct uloop_fd *u, unsigned int events)
         }
 }
 
+static void netns_updown(struct ubus_context *ctx, const char *name, bool start, int netns_fd)
+{
+       static struct blob_buf req;
+       uint32_t id;
+
+       if (!ctx)
+               return;
+
+       blob_buf_init(&req, 0);
+       if (name)
+               blobmsg_add_string(&req, "jail", name);
+
+       blobmsg_add_u8(&req, "start", start);
+
+       if (ubus_lookup_id(ctx, "network", &id) ||
+           ubus_invoke_fd(ctx, id, "netns_updown", req.head, NULL, NULL, 3000, netns_fd)) {
+               INFO("ubus request failed\n");
+       }
+
+       blob_buf_free(&req);
+}
+
+static void jail_network_reload(struct uloop_timeout *t)
+{
+       uint32_t id;
+
+       if (!netifd_ubus_ctx)
+               return;
+
+       if (gen_jail_uci_network())
+               return;
+
+       if (ubus_lookup_id(netifd_ubus_ctx, "network", &id))
+               return;
+
+       ubus_invoke(netifd_ubus_ctx, id, "reload", NULL, NULL, NULL, 3000);
+}
+
+static const struct blobmsg_policy service_watch_policy = { "config", BLOBMSG_TYPE_STRING };
+static struct uloop_timeout jail_network_reload_timeout = { .cb = jail_network_reload, };
+
+static int config_watch_notify_cb(struct ubus_context *ctx, struct ubus_object *obj,
+                          struct ubus_request_data *req, const char *method,
+                          struct blob_attr *msg)
+{
+       struct blob_attr *attr;
+       const char *config;
+
+       if (strcmp(method, "config.change"))
+               return 0;
+
+       blobmsg_parse(&service_watch_policy, 1, &attr, blob_data(msg), blob_len(msg));
+       if (!attr)
+               return 1;
+
+       config = blobmsg_get_string(attr);
+       if (strcmp(config, "network"))
+               return 0;
+
+       uloop_timeout_add(&jail_network_reload_timeout);
+
+       return 0;
+}
+
+static void watch_ubus_service(void)
+{
+       uint32_t id;
+
+       config_watch_subscribe.cb = config_watch_notify_cb;
+       if (ubus_register_subscriber(ctx, &config_watch_subscribe)) {
+               ERROR("failed to register ubus subscriber\n");
+               return;
+       }
+
+       if (ubus_lookup_id(ctx, "service", &id))
+               return;
+
+       if (!ubus_subscribe(ctx, &config_watch_subscribe, id))
+               return;
+
+       ERROR("failed to subscribe %d\n", id);
+}
+
 static struct uloop_timeout ubus_start_timeout = { .cb = run_ubusd, };
 
 int jail_network_start(struct ubus_context *new_ctx, char *new_jail_name, pid_t new_ns_pid)
 {
        ubus_pw = getpwnam("ubus");
        int ret = 0;
+       int netns_fd;
 
        ctx = new_ctx;
        ns_pid = new_ns_pid;
@@ -387,6 +468,13 @@ int jail_network_start(struct ubus_context *new_ctx, char *new_jail_name, pid_t
                goto errout_inotify;
        }
 
+       watch_ubus_service();
+
+       netns_fd = ns_open_pid("net", ns_pid);
+
+       netns_updown(ctx, jail_name, true, netns_fd);
+
+       close(netns_fd);
        uloop_timeout_add(&ubus_start_timeout);
        uloop_run();
 
@@ -402,23 +490,45 @@ errout_dir:
        return ret;
 }
 
+static int jail_delete_instance(const char *instance)
+{
+       static struct blob_buf req;
+       uint32_t id;
+
+       if (ubus_lookup_id(ctx, "container", &id))
+               return -1;
+
+       blob_buf_init(&req, 0);
+       blobmsg_add_string(&req, "name", jail_name);
+       blobmsg_add_string(&req, "instance", instance);
+
+       return ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000);
+}
+
 int jail_network_stop(void)
 {
-       int ret;
+       int host_netns = open("/proc/self/ns/net", O_RDONLY);
 
-       ret = kill_jail_instance("netifd");
-       if (ret)
-               return ret;
+       if (host_netns < 0)
+               return errno;
+
+       netns_updown(netifd_ubus_ctx, NULL, false, host_netns);
+
+       close(host_netns);
+       ubus_free(netifd_ubus_ctx);
+
+       jail_delete_instance("netifd");
+       jail_delete_instance("ubus");
 
        if (uci_config_network) {
                unlink(uci_config_network);
                rmdir(dirname(uci_config_network));
                free(uci_config_network);
-       };
+       }
 
-       ret = kill_jail_instance("ubus");
-       if (ret)
-               return ret;
+       free(ubus_sock_path);
+       rmdir(ubus_sock_dir);
+       free(ubus_sock_dir);
 
        return 0;
 }