bb205d0b241f450142304ed9fd73fbad66791f55
[project/procd.git] / jail / netifd.c
1 /*
2 * Copyright (C) 2021 Daniel Golle <daniel@makrotopia.org>
3 *
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
7 *
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.
12 *
13 * launch private ubus and netifd instances for containers with managed
14 * network namespace.
15 */
16
17 #define _GNU_SOURCE /* See feature_test_macros(7) */
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <libgen.h>
23 #include <fcntl.h>
24
25 #include <sys/inotify.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28
29 #include <pwd.h>
30
31 #include <linux/limits.h>
32
33 #include <libubox/uloop.h>
34 #include <libubox/utils.h>
35 #include <libubus.h>
36 #include <libubox/blobmsg.h>
37 #include <libubox/blobmsg_json.h>
38 #include <uci.h>
39
40 #include "netifd.h"
41 #include "log.h"
42 #include "jail.h"
43
44 #define INOTIFY_SZ (sizeof(struct inotify_event) + PATH_MAX + 1)
45
46 static const char ubusd_path[] = "/sbin/ubusd";
47 static const char netifd_path[] = "/sbin/netifd";
48 static const char uci_net[] = "network";
49
50 static char *jail_name, *ubus_sock_path, *ubus_sock_dir, *uci_config_network = NULL;
51
52 static char *inotify_buffer;
53 static struct uloop_fd fd_inotify_read;
54 static struct passwd *ubus_pw;
55 static pid_t ns_pid;
56
57 static struct ubus_context *host_ubus_ctx = NULL;
58 static struct ubus_context *jail_ubus_ctx = NULL;
59
60 static struct ubus_subscriber config_watch_subscribe;
61
62 /* generate /etc/config/network for jail'ed netifd */
63 static int gen_jail_uci_network(void)
64 {
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;
69 int ret = 0;
70 FILE *ucinetf;
71
72 /* if no network configuration is active just return */
73 if (!uci_config_network)
74 goto uci_out;
75
76 /* open output uci network config file */
77 ucinetf = fopen(uci_config_network, "w");
78 if (!ucinetf) {
79 ret = errno;
80 goto uci_out;
81 }
82
83 /* load network uci package */
84 if (uci_load(uci_ctx, uci_net, &pkg) != UCI_OK) {
85 char *err;
86 uci_get_errorstr(uci_ctx, &err, uci_net);
87 fprintf(stderr, "unable to load configuration (%s)\n", err);
88 free(err);
89 ret = EIO;
90 goto ucinetf_out;
91 }
92
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 };
98
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)) {
101 ptr.o = o;
102 struct uci_option *jio = uci_lookup_option(uci_ctx, s, "jail_device");
103 if (!jio)
104 jio = uci_lookup_option(uci_ctx, s, "jail_ifname");
105
106 if (jio) {
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);
113 }
114
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);
118 }
119 }
120
121 uci_delete(uci_ctx, &ptr);
122 }
123
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"))
128 continue;
129
130 const char *devname = uci_lookup_option_string(uci_ctx, s, "device");
131 if (devname && !strcmp(devname, "lo")) {
132 has_loopback = true;
133 break;
134 }
135 }
136
137 /* create loopback interface section if not defined */
138 if (!has_loopback) {
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);
150 }
151
152 ret = uci_export(uci_ctx, ucinetf, pkg, false);
153
154 ucinetf_out:
155 fclose(ucinetf);
156
157 uci_out:
158 uci_free_context(uci_ctx);
159
160 return ret;
161 }
162
163 static void run_ubusd(struct uloop_timeout *t)
164 {
165 static struct blob_buf req;
166 void *ins, *in, *cmd;
167 uint32_t id;
168
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);
178
179 if (ubus_pw) {
180 blobmsg_add_string(&req, "user", "ubus");
181 blobmsg_add_string(&req, "group", "ubus");
182 }
183
184 blobmsg_close_table(&req, in);
185 blobmsg_close_table(&req, ins);
186
187 if (!ubus_lookup_id(host_ubus_ctx, "container", &id))
188 ubus_invoke(host_ubus_ctx, id, "add", req.head, NULL, NULL, 3000);
189
190 blob_buf_free(&req);
191 }
192
193 static void run_netifd(struct uloop_timeout *t)
194 {
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";
199
200 uint32_t id;
201 bool running = false;
202
203 uloop_fd_delete(&fd_inotify_read);
204 close(fd_inotify_read.fd);
205
206 jail_ubus_ctx = ubus_connect(ubus_sock_path);
207 if (!jail_ubus_ctx)
208 return;
209
210 if (asprintf(&resolvconf_dir, "/tmp/resolv.conf-%s.d", jail_name) == -1)
211 return;
212
213 if (asprintf(&resolvconf, "%s/resolv.conf.auto", resolvconf_dir) == -1)
214 goto netifd_out_resolvconf_dir;
215
216 if (!mkdtemp(uci_dir))
217 goto netifd_out_resolvconf;
218
219 if (asprintf(&uci_config_network, "%s/network", uci_dir) == -1)
220 goto netifd_out_ucidir;
221
222 if (asprintf(&ucimount, "%s:/etc/config", uci_dir) == -1)
223 goto netifd_out_ucinetconf;
224
225 if (gen_jail_uci_network())
226 goto netifd_out_ucimount;
227
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");
232
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);
240
241 jail = blobmsg_open_table(&req, "jail");
242
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);
253
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);
268
269 blobmsg_add_u8(&req, "log", 1);
270 blobmsg_add_u8(&req, "procfs", 1);
271 blobmsg_add_u8(&req, "sysfs", 1);
272
273 blobmsg_add_u8(&req, "requirejail", 1);
274
275 blobmsg_close_table(&req, jail);
276
277 blobmsg_add_u8(&req, "stdout", 1);
278 blobmsg_add_u8(&req, "stderr", 1);
279
280 blobmsg_close_table(&req, in);
281 blobmsg_close_table(&req, ins);
282
283 if (!ubus_lookup_id(host_ubus_ctx, "container", &id))
284 running = !ubus_invoke(host_ubus_ctx, id, "add", req.head, NULL, NULL, 3000);
285
286 if (!running)
287 blob_buf_free(&req);
288 netifd_out_ucimount:
289 free(ucimount);
290 netifd_out_ucinetconf:
291 if (!running) {
292 unlink(uci_config_network);
293 free(uci_config_network);
294 }
295 netifd_out_ucidir:
296 if (!running)
297 rmdir(uci_dir);
298 netifd_out_resolvconf:
299 free(resolvconf);
300 netifd_out_resolvconf_dir:
301 free(resolvconf_dir);
302
303 uloop_end();
304 }
305
306 static struct uloop_timeout netifd_start_timeout = { .cb = run_netifd, };
307
308 static void inotify_read_handler(struct uloop_fd *u, unsigned int events)
309 {
310 int rc;
311 char *p;
312 struct inotify_event *in;
313
314 /* read inotify events */
315 while ((rc = read(u->fd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
316
317 if (rc <= 0)
318 return;
319
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;
325
326 if (in->len < 4)
327 continue;
328
329 if (!strncmp("ubus", in->name, in->len))
330 uloop_timeout_add(&netifd_start_timeout);
331 }
332 }
333
334 static void netns_updown(struct ubus_context *ubus, const char *name, bool start, int netns_fd)
335 {
336 static struct blob_buf req;
337 uint32_t id;
338
339 if (!ubus)
340 return;
341
342 blob_buf_init(&req, 0);
343 if (name)
344 blobmsg_add_string(&req, "jail", name);
345
346 blobmsg_add_u8(&req, "start", start);
347
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");
351 }
352
353 blob_buf_free(&req);
354 }
355
356 static void jail_network_reload(struct uloop_timeout *t)
357 {
358 uint32_t id;
359
360 if (!jail_ubus_ctx)
361 return;
362
363 if (gen_jail_uci_network())
364 return;
365
366 if (ubus_lookup_id(jail_ubus_ctx, "network", &id))
367 return;
368
369 ubus_invoke(jail_ubus_ctx, id, "reload", NULL, NULL, NULL, 3000);
370 }
371
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, };
374
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)
378 {
379 struct blob_attr *attr;
380 const char *config;
381
382 if (strcmp(method, "config.change"))
383 return 0;
384
385 blobmsg_parse(&service_watch_policy, 1, &attr, blob_data(msg), blob_len(msg));
386 if (!attr)
387 return 1;
388
389 config = blobmsg_get_string(attr);
390 if (strcmp(config, "network"))
391 return 0;
392
393 uloop_timeout_add(&jail_network_reload_timeout);
394
395 return 0;
396 }
397
398 static void watch_ubus_service(void)
399 {
400 uint32_t id;
401
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");
405 return;
406 }
407
408 if (ubus_lookup_id(host_ubus_ctx, "service", &id))
409 return;
410
411 if (!ubus_subscribe(host_ubus_ctx, &config_watch_subscribe, id))
412 return;
413
414 ERROR("failed to subscribe %d\n", id);
415 }
416
417 static struct uloop_timeout ubus_start_timeout = { .cb = run_ubusd, };
418
419 int jail_network_start(struct ubus_context *new_ctx, char *new_jail_name, pid_t new_ns_pid)
420 {
421 ubus_pw = getpwnam("ubus");
422 int ret = 0;
423 int netns_fd;
424
425 host_ubus_ctx = new_ctx;
426 ns_pid = new_ns_pid;
427 jail_name = new_jail_name;
428
429 if (asprintf(&ubus_sock_dir, "/var/containers/ubus-%s", jail_name) == -1) {
430 ret = ENOMEM;
431 goto errout_dir;
432 }
433
434 if (asprintf(&ubus_sock_path, "%s/ubus", ubus_sock_dir) == -1) {
435 ret = ENOMEM;
436 goto errout_path;
437 }
438
439 mkdir_p(ubus_sock_dir, 0755);
440 if (ubus_pw) {
441 ret = chown(ubus_sock_dir, ubus_pw->pw_uid, ubus_pw->pw_gid);
442 if (ret) {
443 ret = errno;
444 goto errout;
445 }
446 }
447
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");
452 ret = EIO;
453 goto errout;
454 }
455 uloop_fd_add(&fd_inotify_read, ULOOP_READ);
456
457 inotify_buffer = calloc(1, INOTIFY_SZ);
458 if (!inotify_buffer) {
459 ret = ENOMEM;
460 goto errout_inotify;
461 }
462
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);
466 ret = EIO;
467 goto errout_inotify;
468 }
469
470 watch_ubus_service();
471
472 netns_fd = ns_open_pid("net", ns_pid);
473 if (netns_fd < 0) {
474 ret = ESRCH;
475 goto errout_inotify;
476 }
477
478 netns_updown(host_ubus_ctx, jail_name, true, netns_fd);
479
480 close(netns_fd);
481 uloop_timeout_add(&ubus_start_timeout);
482 uloop_run();
483
484 return 0;
485
486 errout_inotify:
487 close(fd_inotify_read.fd);
488 errout:
489 free(ubus_sock_path);
490 errout_path:
491 free(ubus_sock_dir);
492 errout_dir:
493 return ret;
494 }
495
496 static int jail_delete_instance(const char *instance)
497 {
498 static struct blob_buf req;
499 uint32_t id;
500
501 if (ubus_lookup_id(host_ubus_ctx, "container", &id))
502 return -1;
503
504 blob_buf_init(&req, 0);
505 blobmsg_add_string(&req, "name", jail_name);
506 blobmsg_add_string(&req, "instance", instance);
507
508 return ubus_invoke(host_ubus_ctx, id, "delete", req.head, NULL, NULL, 3000);
509 }
510
511 int jail_network_stop(void)
512 {
513 int host_netns = open("/proc/self/ns/net", O_RDONLY);
514
515 if (host_netns < 0)
516 return errno;
517
518 netns_updown(jail_ubus_ctx, NULL, false, host_netns);
519
520 close(host_netns);
521 ubus_free(jail_ubus_ctx);
522
523 jail_delete_instance("netifd");
524 jail_delete_instance("ubus");
525
526 if (uci_config_network) {
527 unlink(uci_config_network);
528 rmdir(dirname(uci_config_network));
529 free(uci_config_network);
530 }
531
532 free(ubus_sock_path);
533 rmdir(ubus_sock_dir);
534 free(ubus_sock_dir);
535
536 return 0;
537 }