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