From 1321c1bd8fe921986c4eb39c3783ddd827b79543 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Mon, 30 Dec 2019 14:57:47 +0200 Subject: [PATCH] add basic support for jail network namespaces 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 --- interface-ip.c | 43 ++++++++++++++++------- interface-ip.h | 2 +- interface.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++-- interface.h | 4 +++ system-dummy.c | 18 ++++++++++ system-linux.c | 35 +++++++++++++++++++ system.h | 4 +++ ubus.c | 43 +++++++++++++++++++++++ 8 files changed, 225 insertions(+), 16 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index c159e09..91c305b 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include @@ -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 diff --git a/interface-ip.h b/interface-ip.h index 3f99eb9..5ab9299 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -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); diff --git a/interface.c b/interface.c index 028dc6c..f661cfe 100644 --- a/interface.c +++ b/interface.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #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); diff --git a/interface.h b/interface.h index 22738a9..0d384ef 100644 --- a/interface.h +++ b/interface.h @@ -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 diff --git a/system-dummy.c b/system-dummy.c index 58fd2d0..9c37bd5 100644 --- a/system-dummy.c +++ b/system-dummy.c @@ -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); diff --git a/system-linux.c b/system-linux.c index acfd40e..d533be8 100644 --- a/system-linux.c +++ b/system-linux.c @@ -45,6 +45,8 @@ #include #include +#include + #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; diff --git a/system.h b/system.h index 61c4046..fe4497e 100644 --- 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 5a2a339..85d834d 100644 --- 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"); -- 2.30.2