Browse Source

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 <daniel@makrotopia.org>
Daniel Golle 4 years ago
parent
commit
1321c1bd8f
8 changed files with 225 additions and 16 deletions
  1. 30 13
      interface-ip.c
  2. 1 1
      interface-ip.h
  3. 90 2
      interface.c
  4. 4 0
      interface.h
  5. 18 0
      system-dummy.c
  6. 35 0
      system-linux.c
  7. 4 0
      system.h
  8. 43 0
      ubus.c

+ 30 - 13
interface-ip.c

@@ -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

+ 1 - 1
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);

+ 90 - 2
interface.c

@@ -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);
 

+ 4 - 0
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

+ 18 - 0
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);

+ 35 - 0
system-linux.c

@@ -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;

+ 4 - 0
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

+ 43 - 0
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");