8238c0d188f7154c11f978bf502975e2307154ec
[project/netifd.git] / proto-shell.c
1 #define _GNU_SOURCE
2
3 #include <string.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <glob.h>
7 #include <unistd.h>
8 #include <fcntl.h>
9 #include <signal.h>
10
11 #include <arpa/inet.h>
12 #include <netinet/in.h>
13
14 #include <libubox/blobmsg_json.h>
15
16 #include "netifd.h"
17 #include "interface.h"
18 #include "interface-ip.h"
19 #include "proto.h"
20
21 static struct netifd_fd proto_fd;
22
23 enum proto_shell_sm {
24 S_IDLE,
25 S_SETUP,
26 S_SETUP_ABORT,
27 S_TEARDOWN,
28 };
29
30 struct proto_shell_handler {
31 struct list_head list;
32 struct proto_handler proto;
33 struct config_param_list config;
34 char *config_buf;
35 bool init_available;
36 char script_name[];
37 };
38
39 struct proto_shell_state {
40 struct interface_proto_state proto;
41 struct proto_shell_handler *handler;
42 struct blob_attr *config;
43
44 struct device_user l3_dev;
45
46 struct uloop_timeout teardown_timeout;
47
48 struct netifd_process script_task;
49 struct netifd_process proto_task;
50
51 enum proto_shell_sm sm;
52 bool proto_task_killed;
53
54 int last_error;
55 };
56
57 static int
58 proto_shell_handler(struct interface_proto_state *proto,
59 enum interface_proto_cmd cmd, bool force)
60 {
61 struct proto_shell_state *state;
62 struct proto_shell_handler *handler;
63 struct netifd_process *proc;
64 static char error_buf[32];
65 const char *argv[7];
66 char *envp[2];
67 const char *action;
68 char *config;
69 int ret, i = 0, j = 0;
70
71 state = container_of(proto, struct proto_shell_state, proto);
72 handler = state->handler;
73 proc = &state->script_task;
74
75 if (cmd == PROTO_CMD_SETUP) {
76 action = "setup";
77 state->last_error = -1;
78 } else {
79 if (state->sm == S_TEARDOWN)
80 return 0;
81
82 if (state->l3_dev.dev)
83 device_remove_user(&state->l3_dev);
84
85 if (state->script_task.uloop.pending) {
86 if (state->sm != S_SETUP_ABORT) {
87 uloop_timeout_set(&state->teardown_timeout, 1000);
88 kill(state->script_task.uloop.pid, SIGTERM);
89 if (state->proto_task.uloop.pending)
90 kill(state->proto_task.uloop.pid, SIGTERM);
91 state->sm = S_SETUP_ABORT;
92 }
93 return 0;
94 }
95
96 action = "teardown";
97 state->sm = S_TEARDOWN;
98 if (state->last_error >= 0) {
99 snprintf(error_buf, sizeof(error_buf), "ERROR=%d", state->last_error);
100 envp[j++] = error_buf;
101 }
102 uloop_timeout_set(&state->teardown_timeout, 5000);
103 }
104
105 config = blobmsg_format_json(state->config, true);
106 if (!config)
107 return -1;
108
109 argv[i++] = handler->script_name;
110 argv[i++] = handler->proto.name;
111 argv[i++] = action;
112 argv[i++] = proto->iface->name;
113 argv[i++] = config;
114 if (proto->iface->main_dev.dev)
115 argv[i++] = proto->iface->main_dev.dev->ifname;
116 argv[i] = NULL;
117 envp[j] = NULL;
118
119 ret = netifd_start_process(argv, envp, proc);
120 free(config);
121
122 return ret;
123 }
124
125 static void
126 proto_shell_task_finish(struct proto_shell_state *state,
127 struct netifd_process *task)
128 {
129 switch (state->sm) {
130 case S_IDLE:
131 if (task == &state->proto_task)
132 state->proto.proto_event(&state->proto, IFPEV_LINK_LOST);
133 /* fall through */
134 case S_SETUP:
135 if (task == &state->proto_task)
136 proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN,
137 false);
138 break;
139
140 case S_SETUP_ABORT:
141 if (state->script_task.uloop.pending ||
142 state->proto_task.uloop.pending)
143 break;
144
145 uloop_timeout_cancel(&state->teardown_timeout);
146 state->sm = S_IDLE;
147 proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false);
148 break;
149
150 case S_TEARDOWN:
151 if (state->script_task.uloop.pending)
152 break;
153
154 if (state->proto_task.uloop.pending) {
155 if (!state->proto_task_killed)
156 kill(state->proto_task.uloop.pid, SIGTERM);
157 break;
158 }
159
160 uloop_timeout_cancel(&state->teardown_timeout);
161 state->sm = S_IDLE;
162 state->proto.proto_event(&state->proto, IFPEV_DOWN);
163 break;
164 }
165 }
166
167 static void
168 proto_shell_teardown_timeout_cb(struct uloop_timeout *timeout)
169 {
170 struct proto_shell_state *state;
171
172 state = container_of(timeout, struct proto_shell_state, teardown_timeout);
173
174 netifd_kill_process(&state->script_task);
175 netifd_kill_process(&state->proto_task);
176 proto_shell_task_finish(state, NULL);
177 }
178
179 static void
180 proto_shell_script_cb(struct netifd_process *p, int ret)
181 {
182 struct proto_shell_state *state;
183
184 state = container_of(p, struct proto_shell_state, script_task);
185 proto_shell_task_finish(state, p);
186 }
187
188 static void
189 proto_shell_task_cb(struct netifd_process *p, int ret)
190 {
191 struct proto_shell_state *state;
192
193 state = container_of(p, struct proto_shell_state, proto_task);
194
195 if (state->sm == S_IDLE || state->sm == S_SETUP)
196 state->last_error = WEXITSTATUS(ret);
197
198 proto_shell_task_finish(state, p);
199 }
200
201 static void
202 proto_shell_free(struct interface_proto_state *proto)
203 {
204 struct proto_shell_state *state;
205
206 state = container_of(proto, struct proto_shell_state, proto);
207 if (state->l3_dev.dev)
208 device_remove_user(&state->l3_dev);
209 netifd_kill_process(&state->script_task);
210 netifd_kill_process(&state->proto_task);
211 free(state->config);
212 free(state);
213 }
214
215 static void
216 proto_shell_parse_route_list(struct interface *iface, struct blob_attr *attr,
217 bool v6)
218 {
219 struct blob_attr *cur;
220 int rem;
221
222 blobmsg_for_each_attr(cur, attr, rem) {
223 if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) {
224 DPRINTF("Ignore wrong route type: %d\n", blobmsg_type(cur));
225 continue;
226 }
227
228 interface_ip_add_route(iface, cur, v6);
229 }
230 }
231
232 static struct device *
233 proto_shell_create_tunnel(const char *name, struct blob_attr *attr)
234 {
235 struct device *dev;
236 struct blob_buf b;
237
238 memset(&b, 0, sizeof(b));
239 blob_buf_init(&b, 0);
240 blob_put(&b, 0, blobmsg_data(attr), blobmsg_data_len(attr));
241 dev = device_create(name, &tunnel_device_type, blob_data(b.head));
242 blob_buf_free(&b);
243
244 return dev;
245 }
246
247 enum {
248 NOTIFY_ACTION,
249 NOTIFY_ERROR,
250 NOTIFY_COMMAND,
251 NOTIFY_ENV,
252 NOTIFY_SIGNAL,
253 NOTIFY_AVAILABLE,
254 NOTIFY_LINK_UP,
255 NOTIFY_IFNAME,
256 NOTIFY_ADDR_EXT,
257 NOTIFY_ROUTES,
258 NOTIFY_ROUTES6,
259 NOTIFY_TUNNEL,
260 __NOTIFY_LAST
261 };
262
263 static const struct blobmsg_policy notify_attr[__NOTIFY_LAST] = {
264 [NOTIFY_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_INT32 },
265 [NOTIFY_ERROR] = { .name = "error", .type = BLOBMSG_TYPE_ARRAY },
266 [NOTIFY_COMMAND] = { .name = "command", .type = BLOBMSG_TYPE_ARRAY },
267 [NOTIFY_ENV] = { .name = "env", .type = BLOBMSG_TYPE_ARRAY },
268 [NOTIFY_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 },
269 [NOTIFY_AVAILABLE] = { .name = "available", .type = BLOBMSG_TYPE_BOOL },
270 [NOTIFY_LINK_UP] = { .name = "link-up", .type = BLOBMSG_TYPE_BOOL },
271 [NOTIFY_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
272 [NOTIFY_ADDR_EXT] = { .name = "address-external", .type = BLOBMSG_TYPE_BOOL },
273 [NOTIFY_ROUTES] = { .name = "routes", .type = BLOBMSG_TYPE_ARRAY },
274 [NOTIFY_ROUTES6] = { .name = "routes6", .type = BLOBMSG_TYPE_ARRAY },
275 [NOTIFY_TUNNEL] = { .name = "tunnel", .type = BLOBMSG_TYPE_TABLE },
276 };
277
278 static int
279 proto_shell_update_link(struct proto_shell_state *state, struct blob_attr *data, struct blob_attr **tb)
280 {
281 struct interface *iface = state->proto.iface;
282 struct blob_attr *cur;
283 struct device *dev;
284 const char *devname;
285 int dev_create = 1;
286 bool addr_ext = false;
287 bool up;
288
289 if (!tb[NOTIFY_LINK_UP])
290 return UBUS_STATUS_INVALID_ARGUMENT;
291
292 up = blobmsg_get_bool(tb[NOTIFY_LINK_UP]);
293 if (!up) {
294 state->proto.proto_event(&state->proto, IFPEV_LINK_LOST);
295 return 0;
296 }
297
298 if ((cur = tb[NOTIFY_ADDR_EXT]) != NULL) {
299 addr_ext = blobmsg_get_bool(cur);
300 if (addr_ext)
301 dev_create = 2;
302 }
303
304 if (!tb[NOTIFY_IFNAME]) {
305 if (!iface->main_dev.dev)
306 return UBUS_STATUS_INVALID_ARGUMENT;
307 } else {
308 if (state->l3_dev.dev)
309 device_remove_user(&state->l3_dev);
310
311 devname = blobmsg_data(tb[NOTIFY_IFNAME]);
312 if (tb[NOTIFY_TUNNEL]) {
313 dev = proto_shell_create_tunnel(devname,
314 tb[NOTIFY_TUNNEL]);
315 if (!dev)
316 return UBUS_STATUS_INVALID_ARGUMENT;
317 } else {
318 dev = device_get(devname, dev_create);
319 if (!dev)
320 return UBUS_STATUS_NOT_FOUND;
321 }
322
323 device_add_user(&state->l3_dev, dev);
324 iface->l3_dev = &state->l3_dev;
325 device_claim(&state->l3_dev);
326 }
327
328 interface_update_start(iface);
329 proto_apply_ip_settings(iface, data, addr_ext);
330
331 if ((cur = tb[NOTIFY_ROUTES]) != NULL)
332 proto_shell_parse_route_list(state->proto.iface, cur, false);
333
334 if ((cur = tb[NOTIFY_ROUTES6]) != NULL)
335 proto_shell_parse_route_list(state->proto.iface, cur, true);
336
337 interface_update_complete(state->proto.iface);
338
339 state->proto.proto_event(&state->proto, IFPEV_UP);
340
341 return 0;
342 }
343
344 static bool
345 fill_string_list(struct blob_attr *attr, char **argv, int max)
346 {
347 struct blob_attr *cur;
348 int argc = 0;
349 int rem;
350
351 if (!attr)
352 goto out;
353
354 blobmsg_for_each_attr(cur, attr, rem) {
355 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
356 return false;
357
358 if (!blobmsg_check_attr(cur, NULL))
359 return false;
360
361 argv[argc++] = blobmsg_data(cur);
362 if (argc == max - 1)
363 return false;
364 }
365
366 out:
367 argv[argc] = NULL;
368 return true;
369 }
370
371 static int
372 proto_shell_run_command(struct proto_shell_state *state, struct blob_attr **tb)
373 {
374 static char *argv[64];
375 static char *env[32];
376
377 if (!tb[NOTIFY_COMMAND])
378 goto error;
379
380 if (!fill_string_list(tb[NOTIFY_COMMAND], argv, ARRAY_SIZE(argv)))
381 goto error;
382
383 if (!fill_string_list(tb[NOTIFY_ENV], env, ARRAY_SIZE(env)))
384 goto error;
385
386 netifd_start_process((const char **) argv, (char **) env, &state->proto_task);
387
388 return 0;
389
390 error:
391 return UBUS_STATUS_INVALID_ARGUMENT;
392 }
393
394 static int
395 proto_shell_kill_command(struct proto_shell_state *state, struct blob_attr **tb)
396 {
397 unsigned int signal = ~0;
398
399 if (tb[NOTIFY_SIGNAL])
400 signal = blobmsg_get_u32(tb[NOTIFY_SIGNAL]);
401
402 if (signal > 31)
403 signal = SIGTERM;
404
405 if (state->proto_task.uloop.pending) {
406 state->proto_task_killed = true;
407 kill(state->proto_task.uloop.pid, signal);
408 }
409
410 return 0;
411 }
412
413 static int
414 proto_shell_notify_error(struct proto_shell_state *state, struct blob_attr **tb)
415 {
416 struct blob_attr *cur;
417 char *data[16];
418 int n_data = 0;
419 int rem;
420
421 if (!tb[NOTIFY_ERROR])
422 return UBUS_STATUS_INVALID_ARGUMENT;
423
424 blobmsg_for_each_attr(cur, tb[NOTIFY_ERROR], rem) {
425 if (n_data + 1 == ARRAY_SIZE(data))
426 goto error;
427
428 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
429 goto error;
430
431 if (!blobmsg_check_attr(cur, NULL))
432 goto error;
433
434 data[n_data++] = blobmsg_data(cur);
435 }
436
437 if (!n_data)
438 goto error;
439
440 interface_add_error(state->proto.iface, state->handler->proto.name,
441 data[0], (const char **) &data[1], n_data - 1);
442
443 return 0;
444
445 error:
446 return UBUS_STATUS_INVALID_ARGUMENT;
447 }
448
449 static int
450 proto_shell_block_restart(struct proto_shell_state *state, struct blob_attr **tb)
451 {
452 state->proto.iface->autostart = false;
453 return 0;
454 }
455
456 static int
457 proto_shell_set_available(struct proto_shell_state *state, struct blob_attr **tb)
458 {
459 if (!tb[NOTIFY_AVAILABLE])
460 return UBUS_STATUS_INVALID_ARGUMENT;
461
462 interface_set_available(state->proto.iface, blobmsg_get_bool(tb[NOTIFY_AVAILABLE]));
463 return 0;
464 }
465
466 static int
467 proto_shell_notify(struct interface_proto_state *proto, struct blob_attr *attr)
468 {
469 struct proto_shell_state *state;
470 struct blob_attr *tb[__NOTIFY_LAST];
471
472 state = container_of(proto, struct proto_shell_state, proto);
473
474 blobmsg_parse(notify_attr, __NOTIFY_LAST, tb, blob_data(attr), blob_len(attr));
475 if (!tb[NOTIFY_ACTION])
476 return UBUS_STATUS_INVALID_ARGUMENT;
477
478 switch(blobmsg_get_u32(tb[NOTIFY_ACTION])) {
479 case 0:
480 return proto_shell_update_link(state, attr, tb);
481 case 1:
482 return proto_shell_run_command(state, tb);
483 case 2:
484 return proto_shell_kill_command(state, tb);
485 case 3:
486 return proto_shell_notify_error(state, tb);
487 case 4:
488 return proto_shell_block_restart(state, tb);
489 case 5:
490 return proto_shell_set_available(state, tb);
491 default:
492 return UBUS_STATUS_INVALID_ARGUMENT;
493 }
494 }
495
496 static struct interface_proto_state *
497 proto_shell_attach(const struct proto_handler *h, struct interface *iface,
498 struct blob_attr *attr)
499 {
500 struct proto_shell_state *state;
501
502 state = calloc(1, sizeof(*state));
503 state->config = malloc(blob_pad_len(attr));
504 if (!state->config)
505 goto error;
506
507 memcpy(state->config, attr, blob_pad_len(attr));
508 state->proto.free = proto_shell_free;
509 state->proto.notify = proto_shell_notify;
510 state->proto.cb = proto_shell_handler;
511 state->teardown_timeout.cb = proto_shell_teardown_timeout_cb;
512 state->script_task.cb = proto_shell_script_cb;
513 state->script_task.dir_fd = proto_fd.fd;
514 state->script_task.log_prefix = iface->name;
515 state->proto_task.cb = proto_shell_task_cb;
516 state->proto_task.dir_fd = proto_fd.fd;
517 state->proto_task.log_prefix = iface->name;
518 state->handler = container_of(h, struct proto_shell_handler, proto);
519
520 return &state->proto;
521
522 error:
523 free(state);
524 return NULL;
525 }
526
527 static json_object *
528 check_type(json_object *obj, json_type type)
529 {
530 if (!obj)
531 return NULL;
532
533 if (json_object_get_type(obj) != type)
534 return NULL;
535
536 return obj;
537 }
538
539 static inline json_object *
540 get_field(json_object *obj, const char *name, json_type type)
541 {
542 return check_type(json_object_object_get(obj, name), type);
543 }
544
545 static char *
546 proto_shell_parse_config(struct config_param_list *config, json_object *obj)
547 {
548 struct blobmsg_policy *attrs;
549 char *str_buf, *str_cur;
550 int str_len = 0;
551 int i;
552
553 config->n_params = json_object_array_length(obj);
554 attrs = calloc(1, sizeof(*attrs) * config->n_params);
555 if (!attrs)
556 return NULL;
557
558 config->params = attrs;
559 for (i = 0; i < config->n_params; i++) {
560 json_object *cur, *name, *type;
561
562 cur = check_type(json_object_array_get_idx(obj, i), json_type_array);
563 if (!cur)
564 goto error;
565
566 name = check_type(json_object_array_get_idx(cur, 0), json_type_string);
567 if (!name)
568 goto error;
569
570 type = check_type(json_object_array_get_idx(cur, 1), json_type_int);
571 if (!type)
572 goto error;
573
574 attrs[i].name = json_object_get_string(name);
575 attrs[i].type = json_object_get_int(type);
576 if (attrs[i].type > BLOBMSG_TYPE_LAST)
577 goto error;
578
579 str_len += strlen(attrs[i].name) + 1;
580 }
581
582 str_buf = malloc(str_len);
583 if (!str_buf)
584 goto error;
585
586 str_cur = str_buf;
587 for (i = 0; i < config->n_params; i++) {
588 const char *name = attrs[i].name;
589
590 attrs[i].name = str_cur;
591 str_cur += sprintf(str_cur, "%s", name) + 1;
592 }
593
594 return str_buf;
595
596 error:
597 free(attrs);
598 config->n_params = 0;
599 return NULL;
600 }
601
602 static void
603 proto_shell_add_handler(const char *script, json_object *obj)
604 {
605 struct proto_shell_handler *handler;
606 struct proto_handler *proto;
607 json_object *config, *tmp;
608 const char *name;
609 char *str;
610
611 if (!check_type(obj, json_type_object))
612 return;
613
614 tmp = get_field(obj, "name", json_type_string);
615 if (!tmp)
616 return;
617
618 name = json_object_get_string(tmp);
619
620 handler = calloc(1, sizeof(*handler) +
621 strlen(script) + 1 +
622 strlen(name) + 1);
623 if (!handler)
624 return;
625
626 strcpy(handler->script_name, script);
627
628 str = handler->script_name + strlen(handler->script_name) + 1;
629 strcpy(str, name);
630
631 proto = &handler->proto;
632 proto->name = str;
633 proto->config_params = &handler->config;
634 proto->attach = proto_shell_attach;
635
636 tmp = get_field(obj, "no-device", json_type_boolean);
637 if (tmp && json_object_get_boolean(tmp))
638 handler->proto.flags |= PROTO_FLAG_NODEV;
639
640 tmp = get_field(obj, "available", json_type_boolean);
641 if (tmp && json_object_get_boolean(tmp))
642 handler->proto.flags |= PROTO_FLAG_INIT_AVAILABLE;
643
644 config = get_field(obj, "config", json_type_array);
645 if (config)
646 handler->config_buf = proto_shell_parse_config(&handler->config, config);
647
648 DPRINTF("Add handler for script %s: %s\n", script, proto->name);
649 add_proto_handler(proto);
650 }
651
652 static void proto_shell_add_script(const char *name)
653 {
654 struct json_tokener *tok = NULL;
655 json_object *obj;
656 static char buf[512];
657 char *start, *cmd;
658 FILE *f;
659 int len;
660
661 #define DUMP_SUFFIX " '' dump"
662
663 cmd = alloca(strlen(name) + 1 + sizeof(DUMP_SUFFIX));
664 sprintf(cmd, "%s" DUMP_SUFFIX, name);
665
666 f = popen(cmd, "r");
667 if (!f)
668 return;
669
670 do {
671 start = fgets(buf, sizeof(buf), f);
672 if (!start)
673 continue;
674
675 len = strlen(start);
676
677 if (!tok)
678 tok = json_tokener_new();
679
680 obj = json_tokener_parse_ex(tok, start, len);
681 if (!is_error(obj)) {
682 proto_shell_add_handler(name, obj);
683 json_object_put(obj);
684 json_tokener_free(tok);
685 tok = NULL;
686 } else if (start[len - 1] == '\n') {
687 json_tokener_free(tok);
688 tok = NULL;
689 }
690 } while (!feof(f) && !ferror(f));
691
692 if (tok)
693 json_tokener_free(tok);
694
695 pclose(f);
696 }
697
698 static void __init proto_shell_init(void)
699 {
700 glob_t g;
701 int main_fd;
702 int i;
703
704 main_fd = open(".", O_RDONLY | O_DIRECTORY);
705 if (main_fd < 0)
706 return;
707
708 if (chdir(main_path)) {
709 perror("chdir(main path)");
710 goto close_cur;
711 }
712
713 if (chdir("./proto"))
714 goto close_cur;
715
716 proto_fd.fd = open(".", O_RDONLY | O_DIRECTORY);
717 if (proto_fd.fd < 0)
718 goto close_cur;
719
720 netifd_fd_add(&proto_fd);
721 glob("./*.sh", 0, NULL, &g);
722 for (i = 0; i < g.gl_pathc; i++)
723 proto_shell_add_script(g.gl_pathv[i]);
724
725 close_cur:
726 fchdir(main_fd);
727 close(main_fd);
728 }