add basic support for jail network namespaces
authorDaniel Golle <daniel@makrotopia.org>
Mon, 30 Dec 2019 12:57:47 +0000 (14:57 +0200)
committerDaniel Golle <daniel@makrotopia.org>
Sat, 18 Jan 2020 11:39:45 +0000 (13:39 +0200)
Prepare netifd for handling procd service jails having their own
network namespace.
Intefaces having the jail attribute will only be brought inside the
jail's network namespace by procd calling the newly introduced ubus
method 'netns_updown'.
Currently proto 'static' is supported and configuration changes are
not yet being handled (ie. you'll have to restart the jailed service
for changes to take effect).

Example /etc/config/network snippet:

config device 'veth0'
    option type 'veth'
    option name 'vhost0'
    option peer_name 'virt0'

config interface 'virt'
    option type 'bridge'
    list ifname 'vhost0'
    option proto 'static'
    option ipaddr '10.0.0.1'
    option netmask '255.255.255.0'

config interface 'virt0'
    option ifname 'virt0'
    option proto 'static'
    option ipaddr '10.0.0.2'
    option netmask '255.255.255.0'
    option gateway '10.0.0.1'
    option dns '10.0.0.1'
    option jail 'transmission'

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
interface-ip.c
interface-ip.h
interface.c
interface.h
system-dummy.c
system-linux.c
system.h
ubus.c

index c159e09133165aec461d34c8a42311d7f5e69bd3..91c305b6d562e26a2632a9bb801e19a982960281 100644 (file)
@@ -15,6 +15,8 @@
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <libgen.h>
+#include <sys/stat.h>
 
 #include <limits.h>
 #include <arpa/inet.h>
@@ -1443,7 +1445,7 @@ static int resolv_conf_iface_cmp(const void *k1, const void *k2, void *ptr)
 }
 
 static void
-__interface_write_dns_entries(FILE *f)
+__interface_write_dns_entries(FILE *f, const char *jail)
 {
        struct interface *iface;
        struct {
@@ -1457,6 +1459,9 @@ __interface_write_dns_entries(FILE *f)
                if (iface->state != IFS_UP)
                        continue;
 
+               if (jail && (!iface->jail || strcmp(jail, iface->jail)))
+                       continue;
+
                if (vlist_simple_empty(&iface->proto_ip.dns_search) &&
                    vlist_simple_empty(&iface->proto_ip.dns_servers) &&
                    vlist_simple_empty(&iface->config_ip.dns_search) &&
@@ -1488,21 +1493,33 @@ __interface_write_dns_entries(FILE *f)
 }
 
 void
-interface_write_resolv_conf(void)
+interface_write_resolv_conf(const char *jail)
 {
-       char *path = alloca(strlen(resolv_conf) + 5);
+       size_t plen = (jail ? strlen(jail) + 1 : 0 ) + strlen(resolv_conf) + 1;
+       char *path = alloca(plen);
+       char *dpath = alloca(plen);
+       char *tmppath = alloca(plen + 4);
        FILE *f;
        uint32_t crcold, crcnew;
 
-       sprintf(path, "%s.tmp", resolv_conf);
-       unlink(path);
-       f = fopen(path, "w+");
+       if (jail) {
+               sprintf(path, "/tmp/resolv.conf-%s.d/resolv.conf.auto", jail);
+               strcpy(dpath, path);
+               dpath = dirname(dpath);
+               mkdir(dpath, 0755);
+       } else {
+               strcpy(path, resolv_conf);
+       }
+
+       sprintf(tmppath, "%s.tmp", path);
+       unlink(tmppath);
+       f = fopen(tmppath, "w+");
        if (!f) {
                D(INTERFACE, "Failed to open %s for writing\n", path);
                return;
        }
 
-       __interface_write_dns_entries(f);
+       __interface_write_dns_entries(f, jail);
 
        fflush(f);
        rewind(f);
@@ -1510,17 +1527,17 @@ interface_write_resolv_conf(void)
        fclose(f);
 
        crcold = crcnew + 1;
-       f = fopen(resolv_conf, "r");
+       f = fopen(path, "r");
        if (f) {
                crcold = crc32_file(f);
                fclose(f);
        }
 
        if (crcold == crcnew) {
-               unlink(path);
-       } else if (rename(path, resolv_conf) < 0) {
-               D(INTERFACE, "Failed to replace %s\n", resolv_conf);
-               unlink(path);
+               unlink(tmppath);
+       } else if (rename(tmppath, path) < 0) {
+               D(INTERFACE, "Failed to replace %s\n", path);
+               unlink(tmppath);
        }
 }
 
@@ -1640,7 +1657,7 @@ interface_ip_update_complete(struct interface_ip_settings *ip)
        vlist_flush(&ip->addr);
        vlist_flush(&ip->prefix);
        vlist_flush(&ip->neighbor);
-       interface_write_resolv_conf();
+       interface_write_resolv_conf(ip->iface->jail);
 }
 
 void
index 3f99eb9a48523c00525998d51bca4473aaac053a..5ab92996999c791843c9bd8d4a6bfc8f1f84750f 100644 (file)
@@ -173,7 +173,7 @@ extern struct list_head prefixes;
 void interface_ip_init(struct interface *iface);
 void interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr *list);
 void interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list);
-void interface_write_resolv_conf(void);
+void interface_write_resolv_conf(const char *jail);
 
 void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6);
 void interface_ip_add_neighbor(struct interface *iface, struct blob_attr *attr, bool v6);
index 028dc6ccd5e53fd26ffe498699ba6621461311b9..f661cfe22762cfb4e39a9d13eb4098dd8e7272a2 100644 (file)
@@ -14,6 +14,8 @@
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 
 #include "netifd.h"
 #include "device.h"
@@ -31,6 +33,7 @@ enum {
        IFACE_ATTR_IFNAME,
        IFACE_ATTR_PROTO,
        IFACE_ATTR_AUTO,
+       IFACE_ATTR_JAIL,
        IFACE_ATTR_DEFAULTROUTE,
        IFACE_ATTR_PEERDNS,
        IFACE_ATTR_DNS,
@@ -54,6 +57,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
        [IFACE_ATTR_PROTO] = { .name = "proto", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_AUTO] = { .name = "auto", .type = BLOBMSG_TYPE_BOOL },
+       [IFACE_ATTR_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_DEFAULTROUTE] = { .name = "defaultroute", .type = BLOBMSG_TYPE_BOOL },
        [IFACE_ATTR_PEERDNS] = { .name = "peerdns", .type = BLOBMSG_TYPE_BOOL },
        [IFACE_ATTR_METRIC] = { .name = "metric", .type = BLOBMSG_TYPE_INT32 },
@@ -760,7 +764,7 @@ interface_proto_event_cb(struct interface_proto_state *state, enum interface_pro
                return;
        }
 
-       interface_write_resolv_conf();
+       interface_write_resolv_conf(iface->jail);
 }
 
 void interface_set_proto_state(struct interface *iface, struct interface_proto_state *state)
@@ -886,6 +890,13 @@ interface_alloc(const char *name, struct blob_attr *config, bool dynamic)
        iface->proto_ip.no_delegation = !blobmsg_get_bool_default(tb[IFACE_ATTR_DELEGATE], true);
 
        iface->config_autostart = iface->autostart;
+       iface->jail = NULL;
+
+       if ((cur = tb[IFACE_ATTR_JAIL])) {
+               iface->jail = blobmsg_get_string(cur);
+               iface->autostart = false;
+       }
+
        return iface;
 }
 
@@ -1133,6 +1144,79 @@ interface_start_pending(void)
        }
 }
 
+void
+interface_start_jail(const char *jail, const pid_t netns_pid)
+{
+       struct interface *iface;
+       int netns_fd;
+       int wstatus;
+       pid_t pr = 0;
+
+       netns_fd = system_netns_open(netns_pid);
+       if (netns_fd < 0)
+               return;
+
+       vlist_for_each_element(&interfaces, iface, node) {
+               if (!iface->jail || strcmp(iface->jail, jail))
+                       continue;
+
+               system_link_netns_move(iface->ifname, netns_fd);
+       }
+
+       pr = fork();
+       if (pr) {
+               waitpid(pr, &wstatus, WUNTRACED | WCONTINUED);
+               close(netns_fd);
+               return;
+       }
+
+       system_netns_set(netns_fd);
+       system_init();
+       vlist_for_each_element(&interfaces, iface, node) {
+               if (!iface->jail || strcmp(iface->jail, jail))
+                       continue;
+
+               interface_set_up(iface);
+       }
+       _exit(0);
+}
+
+void
+interface_stop_jail(const char *jail, const pid_t netns_pid)
+{
+       struct interface *iface;
+       int netns_fd, root_netns;
+       int wstatus;
+       pid_t pr = 0;
+
+       netns_fd = system_netns_open(netns_pid);
+       if (netns_fd < 0)
+               return;
+
+       root_netns = system_netns_open(getpid());
+       if (root_netns < 0)
+               return;
+
+       pr = fork();
+       if (pr) {
+               waitpid(pr, &wstatus, WUNTRACED | WCONTINUED);
+               close(netns_fd);
+               close(root_netns);
+               return;
+       }
+
+       system_netns_set(netns_fd);
+       system_init();
+       vlist_for_each_element(&interfaces, iface, node) {
+               if (!iface->jail || strcmp(iface->jail, jail))
+                       continue;
+
+               interface_set_down(iface);
+               system_link_netns_move(iface->ifname, root_netns);
+       }
+       _exit(0);
+}
+
 static void
 set_config_state(struct interface *iface, enum interface_config_state s)
 {
@@ -1241,6 +1325,10 @@ interface_change_config(struct interface *if_old, struct interface *if_new)
 
        if_old->device_config = if_new->device_config;
        if_old->config_autostart = if_new->config_autostart;
+       if_old->jail = if_new->jail;
+       if (if_old->jail)
+               if_old->autostart = false;
+
        if_old->ifname = if_new->ifname;
        if_old->parent_ifname = if_new->parent_ifname;
        if_old->dynamic = if_new->dynamic;
@@ -1285,7 +1373,7 @@ interface_change_config(struct interface *if_old, struct interface *if_new)
        if (update_prefix_delegation)
                interface_update_prefix_delegation(&if_old->proto_ip);
 
-       interface_write_resolv_conf();
+       interface_write_resolv_conf(if_old->jail);
        if (if_old->main_dev.dev)
                interface_check_state(if_old);
 
index 22738a9968fe80f0ff750cc4f05b6f27a089a21e..0d384efec23db6aa8c0417d83be57bb816445717 100644 (file)
@@ -108,6 +108,8 @@ struct interface {
 
        const char *name;
        const char *ifname;
+       const char *jail;
+       int netns_fd;
 
        bool available;
        bool autostart;
@@ -205,5 +207,7 @@ void interface_update_start(struct interface *iface, const bool keep_old);
 void interface_update_complete(struct interface *iface);
 
 void interface_start_pending(void);
+void interface_start_jail(const char *jail, const pid_t netns_pid);
+void interface_stop_jail(const char *jail, const pid_t netns_pid);
 
 #endif
index 58fd2d07937fc38d936c8b4c748108f85e95e88a..9c37bd59c0693e2b974fc66a95d11bc67ab35f87 100644 (file)
@@ -54,6 +54,24 @@ int system_bridge_delif(struct device *bridge, struct device *dev)
        return 0;
 }
 
+int system_link_netns_move(const char *ifname, int netns_fd)
+{
+       D(SYSTEM, "ip link %s netns %d\n", ifname, netns_fd);
+       return 0;
+}
+
+int system_netns_open(const pid_t target_ns)
+{
+       D(SYSTEM, "open netns of pid %d\n", target_ns);
+       return 1;
+}
+
+int system_netns_set(int netns_fd)
+{
+       D(SYSTEM, "set netns %d\n", netns_fd);
+       return 0;
+}
+
 int system_vlan_add(struct device *dev, int id)
 {
        D(SYSTEM, "vconfig add %s %d\n", dev->ifname, id);
index acfd40eaf4f780a8b23ee78bad88e9bc55a5c3e5..d533be8deb9e89179311cf2884168be47df9626f 100644 (file)
@@ -45,6 +45,8 @@
 #include <linux/veth.h>
 #include <linux/version.h>
 
+#include <sched.h>
+
 #ifndef RTN_FAILED_POLICY
 #define RTN_FAILED_POLICY 12
 #endif
@@ -1243,6 +1245,25 @@ nla_put_failure:
        return -ENOMEM;
 }
 
+int system_link_netns_move(const char *ifname, int netns_fd)
+{
+       struct nl_msg *msg;
+       struct ifinfomsg iim = {
+               .ifi_family = AF_UNSPEC,
+               .ifi_index = 0,
+       };
+
+       msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST);
+
+       if (!msg)
+               return -1;
+
+       nlmsg_append(msg, &iim, sizeof(iim), 0);
+       nla_put_string(msg, IFLA_IFNAME, ifname);
+       nla_put_u32(msg, IFLA_NET_NS_FD, netns_fd);
+       return system_rtnl_call(msg);
+}
+
 static int system_link_del(const char *ifname)
 {
        struct nl_msg *msg;
@@ -1266,6 +1287,20 @@ int system_macvlan_del(struct device *macvlan)
        return system_link_del(macvlan->ifname);
 }
 
+int system_netns_open(const pid_t target_ns)
+{
+       char pid_net_path[PATH_MAX];
+
+       snprintf(pid_net_path, sizeof(pid_net_path), "/proc/%u/ns/net", target_ns);
+
+       return open(pid_net_path, O_RDONLY);
+}
+
+int system_netns_set(int netns_fd)
+{
+       return setns(netns_fd, CLONE_NEWNET);
+}
+
 int system_veth_add(struct device *veth, struct veth_config *cfg)
 {
        struct nl_msg *msg;
index 61c404674cd23832c0bd1f4d535b455c3b494950..fe4497ec469b945d30a652c8fe92a193f2b4b65d 100644 (file)
--- a/system.h
+++ b/system.h
@@ -243,4 +243,8 @@ void system_fd_set_cloexec(int fd);
 
 int system_update_ipv6_mtu(struct device *dev, int mtu);
 
+int system_link_netns_move(const char *ifname, const pid_t target_ns);
+int system_netns_open(const pid_t target_ns);
+int system_netns_set(int netns_fd);
+
 #endif
diff --git a/ubus.c b/ubus.c
index 5a2a339982ea66bf7a8d2d2b667feea8128235e7..85d834dd7946aa2728dca570fa2692f00e447906 100644 (file)
--- a/ubus.c
+++ b/ubus.c
@@ -157,12 +157,52 @@ error:
        return UBUS_STATUS_UNKNOWN_ERROR;
 }
 
+enum {
+       NETNS_UPDOWN_JAIL,
+       NETNS_UPDOWN_PID,
+       NETNS_UPDOWN_START,
+       __NETNS_UPDOWN_MAX
+};
+
+static const struct blobmsg_policy netns_updown_policy[__NETNS_UPDOWN_MAX] = {
+       [NETNS_UPDOWN_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING },
+       [NETNS_UPDOWN_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
+       [NETNS_UPDOWN_START] = { .name = "start", .type = BLOBMSG_TYPE_BOOL },
+};
+
+static int
+netifd_netns_updown(struct ubus_context *ctx, struct ubus_object *obj,
+                 struct ubus_request_data *req, const char *method,
+                 struct blob_attr *msg)
+{
+       struct blob_attr *tb[__NETNS_UPDOWN_MAX];
+       char *jail;
+       pid_t netns_pid;
+       bool start;
+
+       blobmsg_parse(netns_updown_policy, __NETNS_UPDOWN_MAX, tb, blob_data(msg), blob_len(msg));
+       if (!tb[NETNS_UPDOWN_JAIL] || !tb[NETNS_UPDOWN_PID])
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       start = tb[NETNS_UPDOWN_START] && blobmsg_get_bool(tb[NETNS_UPDOWN_START]);
+       jail = blobmsg_get_string(tb[NETNS_UPDOWN_JAIL]);
+       netns_pid = blobmsg_get_u32(tb[NETNS_UPDOWN_PID]);
+
+       if (start)
+               interface_start_jail(jail, netns_pid);
+       else
+               interface_stop_jail(jail, netns_pid);
+
+       return UBUS_STATUS_OK;
+}
+
 static struct ubus_method main_object_methods[] = {
        { .name = "restart", .handler = netifd_handle_restart },
        { .name = "reload", .handler = netifd_handle_reload },
        UBUS_METHOD("add_host_route", netifd_add_host_route, route_policy),
        { .name = "get_proto_handlers", .handler = netifd_get_proto_handlers },
        UBUS_METHOD("add_dynamic", netifd_add_dynamic, dynamic_policy),
+       UBUS_METHOD("netns_updown", netifd_netns_updown, netns_updown_policy),
 };
 
 static struct ubus_object_type main_object_type =
@@ -722,6 +762,9 @@ netifd_dump_status(struct interface *iface)
            !(iface->proto_handler->flags & PROTO_FLAG_NODEV))
                blobmsg_add_string(&b, "device", dev->ifname);
 
+       if (iface->jail)
+               blobmsg_add_string(&b, "jail", iface->jail);
+
        if (iface->state == IFS_UP) {
                if (iface->updated) {
                        a = blobmsg_open_array(&b, "updated");