2 * Copyright (C) 2021 Daniel Golle <daniel@makrotopia.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2.1
6 * as published by the Free Software Foundation
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * launch private ubus and netifd instances for containers with managed
17 #define _GNU_SOURCE /* See feature_test_macros(7) */
25 #include <sys/inotify.h>
27 #include <sys/types.h>
31 #include <linux/limits.h>
33 #include <libubox/uloop.h>
34 #include <libubox/utils.h>
36 #include <libubox/blobmsg.h>
37 #include <libubox/blobmsg_json.h>
44 #define INOTIFY_SZ (sizeof(struct inotify_event) + PATH_MAX + 1)
46 static const char ubusd_path
[] = "/sbin/ubusd";
47 static const char netifd_path
[] = "/sbin/netifd";
48 static const char uci_net
[] = "network";
50 static char *jail_name
, *ubus_sock_path
, *ubus_sock_dir
, *uci_config_network
= NULL
;
52 static char *inotify_buffer
;
53 static struct uloop_fd fd_inotify_read
;
54 static struct passwd
*ubus_pw
;
57 static struct ubus_context
*host_ubus_ctx
= NULL
;
58 static struct ubus_context
*jail_ubus_ctx
= NULL
;
60 static struct ubus_subscriber config_watch_subscribe
;
62 /* generate /etc/config/network for jail'ed netifd */
63 static int gen_jail_uci_network(void)
65 struct uci_context
*uci_ctx
= uci_alloc_context();
66 struct uci_package
*pkg
= NULL
;
67 struct uci_element
*e
, *t
;
68 bool has_loopback
= false;
72 /* if no network configuration is active just return */
73 if (!uci_config_network
)
76 /* open output uci network config file */
77 ucinetf
= fopen(uci_config_network
, "w");
83 /* load network uci package */
84 if (uci_load(uci_ctx
, uci_net
, &pkg
) != UCI_OK
) {
86 uci_get_errorstr(uci_ctx
, &err
, uci_net
);
87 fprintf(stderr
, "unable to load configuration (%s)\n", err
);
93 /* remove all sections which don't match jail */
94 uci_foreach_element_safe(&pkg
->sections
, t
, e
) {
95 struct uci_section
*s
= uci_to_section(e
);
96 struct uci_option
*o
= uci_lookup_option(uci_ctx
, s
, "jail");
97 struct uci_ptr ptr
= { .p
= pkg
, .s
= s
};
99 /* keep match, but remove 'jail' option and rename 'jail_ifname' */
100 if (o
&& o
->type
== UCI_TYPE_STRING
&& !strcmp(o
->v
.string
, jail_name
)) {
102 struct uci_option
*jio
= uci_lookup_option(uci_ctx
, s
, "jail_device");
104 jio
= uci_lookup_option(uci_ctx
, s
, "jail_ifname");
107 struct uci_ptr ren_ptr
= { .p
= pkg
, .s
= s
, .o
= jio
, .value
= "device" };
108 struct uci_option
*host_device
= uci_lookup_option(uci_ctx
, s
, "device");
109 struct uci_option
*legacy_ifname
= uci_lookup_option(uci_ctx
, s
, "ifname");
110 if (host_device
&& legacy_ifname
) {
111 struct uci_ptr delif_ptr
= { .p
= pkg
, .s
= s
, .o
= legacy_ifname
};
112 uci_delete(uci_ctx
, &delif_ptr
);
115 struct uci_ptr renif_ptr
= { .p
= pkg
, .s
= s
, .o
= host_device
?:legacy_ifname
, .value
= "host_device" };
116 uci_rename(uci_ctx
, &renif_ptr
);
117 uci_rename(uci_ctx
, &ren_ptr
);
121 uci_delete(uci_ctx
, &ptr
);
124 /* check if device 'lo' is defined by any remaining interfaces */
125 uci_foreach_element(&pkg
->sections
, e
) {
126 struct uci_section
*s
= uci_to_section(e
);
127 if (strcmp(s
->type
, "interface"))
130 const char *devname
= uci_lookup_option_string(uci_ctx
, s
, "device");
131 if (devname
&& !strcmp(devname
, "lo")) {
137 /* create loopback interface section if not defined */
139 struct uci_ptr ptr
= { .p
= pkg
, .section
= "loopback", .value
= "interface" };
140 uci_set(uci_ctx
, &ptr
);
141 uci_reorder_section(uci_ctx
, ptr
.s
, 0);
142 struct uci_ptr ptr1
= { .p
= pkg
, .s
= ptr
.s
, .option
= "device", .value
= "lo" };
143 struct uci_ptr ptr2
= { .p
= pkg
, .s
= ptr
.s
, .option
= "proto", .value
= "static" };
144 struct uci_ptr ptr3
= { .p
= pkg
, .s
= ptr
.s
, .option
= "ipaddr", .value
= "127.0.0.1" };
145 struct uci_ptr ptr4
= { .p
= pkg
, .s
= ptr
.s
, .option
= "netmask", .value
= "255.0.0.0" };
146 uci_set(uci_ctx
, &ptr1
);
147 uci_set(uci_ctx
, &ptr2
);
148 uci_set(uci_ctx
, &ptr3
);
149 uci_set(uci_ctx
, &ptr4
);
152 ret
= uci_export(uci_ctx
, ucinetf
, pkg
, false);
158 uci_free_context(uci_ctx
);
163 static void run_ubusd(struct uloop_timeout
*t
)
165 static struct blob_buf req
;
166 void *ins
, *in
, *cmd
;
169 blob_buf_init(&req
, 0);
170 blobmsg_add_string(&req
, "name", jail_name
);
171 ins
= blobmsg_open_table(&req
, "instances");
172 in
= blobmsg_open_table(&req
, "ubus");
173 cmd
= blobmsg_open_array(&req
, "command");
174 blobmsg_add_string(&req
, "", ubusd_path
);
175 blobmsg_add_string(&req
, "", "-s");
176 blobmsg_add_string(&req
, "", ubus_sock_path
);
177 blobmsg_close_array(&req
, cmd
);
180 blobmsg_add_string(&req
, "user", "ubus");
181 blobmsg_add_string(&req
, "group", "ubus");
184 blobmsg_close_table(&req
, in
);
185 blobmsg_close_table(&req
, ins
);
187 if (!ubus_lookup_id(host_ubus_ctx
, "container", &id
))
188 ubus_invoke(host_ubus_ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000);
193 static void run_netifd(struct uloop_timeout
*t
)
195 static struct blob_buf req
;
196 void *ins
, *in
, *cmd
, *jail
, *setns
, *setnso
, *namespaces
, *mount
;
197 char *resolvconf_dir
, *resolvconf
, *ucimount
;
198 char uci_dir
[] = "/var/containers/ujail-uci-XXXXXX";
201 bool running
= false;
203 uloop_fd_delete(&fd_inotify_read
);
204 close(fd_inotify_read
.fd
);
206 jail_ubus_ctx
= ubus_connect(ubus_sock_path
);
210 if (asprintf(&resolvconf_dir
, "/tmp/resolv.conf-%s.d", jail_name
) == -1)
213 if (asprintf(&resolvconf
, "%s/resolv.conf.auto", resolvconf_dir
) == -1)
214 goto netifd_out_resolvconf_dir
;
216 if (!mkdtemp(uci_dir
))
217 goto netifd_out_resolvconf
;
219 if (asprintf(&uci_config_network
, "%s/network", uci_dir
) == -1)
220 goto netifd_out_ucidir
;
222 if (asprintf(&ucimount
, "%s:/etc/config", uci_dir
) == -1)
223 goto netifd_out_ucinetconf
;
225 if (gen_jail_uci_network())
226 goto netifd_out_ucimount
;
228 blob_buf_init(&req
, 0);
229 blobmsg_add_string(&req
, "name", jail_name
);
230 ins
= blobmsg_open_table(&req
, "instances");
231 in
= blobmsg_open_table(&req
, "netifd");
233 cmd
= blobmsg_open_array(&req
, "command");
234 blobmsg_add_string(&req
, "", netifd_path
);
235 blobmsg_add_string(&req
, "", "-r");
236 blobmsg_add_string(&req
, "", resolvconf
);
237 blobmsg_add_string(&req
, "", "-s");
238 blobmsg_add_string(&req
, "", ubus_sock_path
);
239 blobmsg_close_array(&req
, cmd
);
241 jail
= blobmsg_open_table(&req
, "jail");
243 setns
= blobmsg_open_array(&req
, "setns");
244 setnso
= blobmsg_open_table(&req
, "");
245 blobmsg_add_u32(&req
, "pid", ns_pid
);
246 namespaces
= blobmsg_open_array(&req
, "namespaces");
247 blobmsg_add_string(&req
, "", "net");
248 blobmsg_add_string(&req
, "", "ipc");
249 blobmsg_add_string(&req
, "", "uts");
250 blobmsg_close_array(&req
, namespaces
);
251 blobmsg_close_table(&req
, setnso
);
252 blobmsg_close_array(&req
, setns
);
254 mount
= blobmsg_open_table(&req
, "mount");
255 blobmsg_add_string(&req
, ubus_sock_dir
, "1");
256 blobmsg_add_string(&req
, resolvconf_dir
, "1");
257 blobmsg_add_string(&req
, ucimount
, "0");
258 blobmsg_add_string(&req
, "/etc/hotplug.d", "0");
259 blobmsg_add_string(&req
, "/lib/functions.sh", "0");
260 blobmsg_add_string(&req
, "/lib/netifd", "0");
261 blobmsg_add_string(&req
, "/lib/network", "0");
262 blobmsg_add_string(&req
, "/usr/bin/logger", "0");
263 blobmsg_add_string(&req
, "/usr/bin/jshn", "0");
264 blobmsg_add_string(&req
, "/usr/share/libubox/jshn.sh", "0");
265 blobmsg_add_string(&req
, "/sbin/hotplug-call", "0");
266 blobmsg_add_string(&req
, "/sbin/udhcpc", "0");
267 blobmsg_close_table(&req
, mount
);
269 blobmsg_add_u8(&req
, "log", 1);
270 blobmsg_add_u8(&req
, "procfs", 1);
271 blobmsg_add_u8(&req
, "sysfs", 1);
273 blobmsg_add_u8(&req
, "requirejail", 1);
275 blobmsg_close_table(&req
, jail
);
277 blobmsg_add_u8(&req
, "stdout", 1);
278 blobmsg_add_u8(&req
, "stderr", 1);
280 blobmsg_close_table(&req
, in
);
281 blobmsg_close_table(&req
, ins
);
283 if (!ubus_lookup_id(host_ubus_ctx
, "container", &id
))
284 running
= !ubus_invoke(host_ubus_ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000);
290 netifd_out_ucinetconf
:
292 unlink(uci_config_network
);
293 free(uci_config_network
);
298 netifd_out_resolvconf
:
300 netifd_out_resolvconf_dir
:
301 free(resolvconf_dir
);
306 static struct uloop_timeout netifd_start_timeout
= { .cb
= run_netifd
, };
308 static void inotify_read_handler(struct uloop_fd
*u
, unsigned int events
)
312 struct inotify_event
*in
;
314 /* read inotify events */
315 while ((rc
= read(u
->fd
, inotify_buffer
, INOTIFY_SZ
)) == -1 && errno
== EINTR
);
320 /* process events from buffer */
321 for (p
= inotify_buffer
;
322 rc
- (p
- inotify_buffer
) >= (int)sizeof(struct inotify_event
);
323 p
+= sizeof(struct inotify_event
) + in
->len
) {
324 in
= (struct inotify_event
*)p
;
329 if (!strncmp("ubus", in
->name
, in
->len
))
330 uloop_timeout_add(&netifd_start_timeout
);
334 static void netns_updown(struct ubus_context
*ubus
, const char *name
, bool start
, int netns_fd
)
336 static struct blob_buf req
;
342 blob_buf_init(&req
, 0);
344 blobmsg_add_string(&req
, "jail", name
);
346 blobmsg_add_u8(&req
, "start", start
);
348 if (ubus_lookup_id(ubus
, "network", &id
) ||
349 ubus_invoke_fd(ubus
, id
, "netns_updown", req
.head
, NULL
, NULL
, 3000, netns_fd
)) {
350 INFO("ubus request failed\n");
356 static void jail_network_reload(struct uloop_timeout
*t
)
363 if (gen_jail_uci_network())
366 if (ubus_lookup_id(jail_ubus_ctx
, "network", &id
))
369 ubus_invoke(jail_ubus_ctx
, id
, "reload", NULL
, NULL
, NULL
, 3000);
372 static const struct blobmsg_policy service_watch_policy
= { "config", BLOBMSG_TYPE_STRING
};
373 static struct uloop_timeout jail_network_reload_timeout
= { .cb
= jail_network_reload
, };
375 static int config_watch_notify_cb(struct ubus_context
*ctx
, struct ubus_object
*obj
,
376 struct ubus_request_data
*req
, const char *method
,
377 struct blob_attr
*msg
)
379 struct blob_attr
*attr
;
382 if (strcmp(method
, "config.change"))
385 blobmsg_parse(&service_watch_policy
, 1, &attr
, blob_data(msg
), blob_len(msg
));
389 config
= blobmsg_get_string(attr
);
390 if (strcmp(config
, "network"))
393 uloop_timeout_add(&jail_network_reload_timeout
);
398 static void watch_ubus_service(void)
402 config_watch_subscribe
.cb
= config_watch_notify_cb
;
403 if (ubus_register_subscriber(host_ubus_ctx
, &config_watch_subscribe
)) {
404 ERROR("failed to register ubus subscriber\n");
408 if (ubus_lookup_id(host_ubus_ctx
, "service", &id
))
411 if (!ubus_subscribe(host_ubus_ctx
, &config_watch_subscribe
, id
))
414 ERROR("failed to subscribe %d\n", id
);
417 static struct uloop_timeout ubus_start_timeout
= { .cb
= run_ubusd
, };
419 int jail_network_start(struct ubus_context
*new_ctx
, char *new_jail_name
, pid_t new_ns_pid
)
421 ubus_pw
= getpwnam("ubus");
425 host_ubus_ctx
= new_ctx
;
427 jail_name
= new_jail_name
;
429 if (asprintf(&ubus_sock_dir
, "/var/containers/ubus-%s", jail_name
) == -1) {
434 if (asprintf(&ubus_sock_path
, "%s/ubus", ubus_sock_dir
) == -1) {
439 mkdir_p(ubus_sock_dir
, 0755);
441 ret
= chown(ubus_sock_dir
, ubus_pw
->pw_uid
, ubus_pw
->pw_gid
);
448 fd_inotify_read
.fd
= inotify_init1(IN_NONBLOCK
| IN_CLOEXEC
);
449 fd_inotify_read
.cb
= inotify_read_handler
;
450 if (fd_inotify_read
.fd
== -1) {
451 ERROR("failed to initialize inotify handler\n");
455 uloop_fd_add(&fd_inotify_read
, ULOOP_READ
);
457 inotify_buffer
= calloc(1, INOTIFY_SZ
);
458 if (!inotify_buffer
) {
463 if (inotify_add_watch(fd_inotify_read
.fd
, ubus_sock_dir
, IN_CREATE
) == -1) {
464 ERROR("failed to add inotify watch on %s\n", ubus_sock_dir
);
465 free(inotify_buffer
);
470 watch_ubus_service();
472 netns_fd
= ns_open_pid("net", ns_pid
);
478 netns_updown(host_ubus_ctx
, jail_name
, true, netns_fd
);
481 uloop_timeout_add(&ubus_start_timeout
);
487 close(fd_inotify_read
.fd
);
489 free(ubus_sock_path
);
496 static int jail_delete_instance(const char *instance
)
498 static struct blob_buf req
;
501 if (ubus_lookup_id(host_ubus_ctx
, "container", &id
))
504 blob_buf_init(&req
, 0);
505 blobmsg_add_string(&req
, "name", jail_name
);
506 blobmsg_add_string(&req
, "instance", instance
);
508 return ubus_invoke(host_ubus_ctx
, id
, "delete", req
.head
, NULL
, NULL
, 3000);
511 int jail_network_stop(void)
513 int host_netns
= open("/proc/self/ns/net", O_RDONLY
);
518 netns_updown(jail_ubus_ctx
, NULL
, false, host_netns
);
521 ubus_free(jail_ubus_ctx
);
523 jail_delete_instance("netifd");
524 jail_delete_instance("ubus");
526 if (uci_config_network
) {
527 unlink(uci_config_network
);
528 rmdir(dirname(uci_config_network
));
529 free(uci_config_network
);
532 free(ubus_sock_path
);
533 rmdir(ubus_sock_dir
);