blob: 393cc684725c566089fa933c05f2e64dd5f6085e [file] [log] [blame]
Amit Pundird477f822020-02-07 22:26:08 +05301#include <err.h>
2#include <errno.h>
3#include <libgen.h>
4#include <limits.h>
5#include <linux/qrtr.h>
6#include <linux/netlink.h>
7#include <linux/rtnetlink.h>
8#include <stdbool.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <sys/types.h>
14#include <sys/socket.h>
15#include <unistd.h>
16
17#include "addr.h"
18#include "hash.h"
19#include "list.h"
20#include "map.h"
21#include "ns.h"
22#include "util.h"
23#include "waiter.h"
24
25#include "libqrtr.h"
26#include "logging.h"
27
28static const char *ctrl_pkt_strings[] = {
29 [QRTR_TYPE_HELLO] = "hello",
30 [QRTR_TYPE_BYE] = "bye",
31 [QRTR_TYPE_NEW_SERVER] = "new-server",
32 [QRTR_TYPE_DEL_SERVER] = "del-server",
33 [QRTR_TYPE_DEL_CLIENT] = "del-client",
34 [QRTR_TYPE_RESUME_TX] = "resume-tx",
35 [QRTR_TYPE_EXIT] = "exit",
36 [QRTR_TYPE_PING] = "ping",
37 [QRTR_TYPE_NEW_LOOKUP] = "new-lookup",
38 [QRTR_TYPE_DEL_LOOKUP] = "del-lookup",
39};
40
41#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
42
43struct context {
44 int sock;
45
46 int local_node;
47
48 struct sockaddr_qrtr bcast_sq;
49
50 struct list lookups;
51};
52
53struct server_filter {
54 unsigned int service;
55 unsigned int instance;
56 unsigned int ifilter;
57};
58
59struct lookup {
60 unsigned int service;
61 unsigned int instance;
62
63 struct sockaddr_qrtr sq;
64 struct list_item li;
65};
66
67struct server {
68 unsigned int service;
69 unsigned int instance;
70
71 unsigned int node;
72 unsigned int port;
73 struct map_item mi;
74 struct list_item qli;
75};
76
77struct node {
78 unsigned int id;
79
80 struct map_item mi;
81 struct map services;
82};
83
84static struct map nodes;
85
86static void server_mi_free(struct map_item *mi);
87
88static struct node *node_get(unsigned int node_id)
89{
90 struct map_item *mi;
91 struct node *node;
92 int rc;
93
94 mi = map_get(&nodes, hash_u32(node_id));
95 if (mi)
96 return container_of(mi, struct node, mi);
97
98 node = calloc(1, sizeof(*node));
99 if (!node)
100 return NULL;
101
102 node->id = node_id;
103
104 rc = map_create(&node->services);
105 if (rc)
106 LOGE_AND_EXIT("unable to create map");
107
108 rc = map_put(&nodes, hash_u32(node_id), &node->mi);
109 if (rc) {
110 map_destroy(&node->services);
111 free(node);
112 return NULL;
113 }
114
115 return node;
116}
117
118static int server_match(const struct server *srv, const struct server_filter *f)
119{
120 unsigned int ifilter = f->ifilter;
121
122 if (f->service != 0 && srv->service != f->service)
123 return 0;
124 if (!ifilter && f->instance)
125 ifilter = ~0;
126 return (srv->instance & ifilter) == f->instance;
127}
128
129static int server_query(const struct server_filter *f, struct list *list)
130{
131 struct map_entry *node_me;
132 struct map_entry *me;
133 struct node *node;
134 int count = 0;
135
136 list_init(list);
137 map_for_each(&nodes, node_me) {
138 node = map_iter_data(node_me, struct node, mi);
139
140 map_for_each(&node->services, me) {
141 struct server *srv;
142
143 srv = map_iter_data(me, struct server, mi);
144 if (!server_match(srv, f))
145 continue;
146
147 list_append(list, &srv->qli);
148 ++count;
149 }
150 }
151
152 return count;
153}
154
155static int service_announce_new(struct context *ctx,
156 struct sockaddr_qrtr *dest,
157 struct server *srv)
158{
159 struct qrtr_ctrl_pkt cmsg;
160 int rc;
161
162 LOGD("advertising new server [%d:%x]@[%d:%d]\n",
163 srv->service, srv->instance, srv->node, srv->port);
164
165 cmsg.cmd = cpu_to_le32(QRTR_TYPE_NEW_SERVER);
166 cmsg.server.service = cpu_to_le32(srv->service);
167 cmsg.server.instance = cpu_to_le32(srv->instance);
168 cmsg.server.node = cpu_to_le32(srv->node);
169 cmsg.server.port = cpu_to_le32(srv->port);
170
171 rc = sendto(ctx->sock, &cmsg, sizeof(cmsg), 0,
172 (struct sockaddr *)dest, sizeof(*dest));
173 if (rc < 0)
174 PLOGW("sendto()");
175
176 return rc;
177}
178
179static int service_announce_del(struct context *ctx,
180 struct sockaddr_qrtr *dest,
181 struct server *srv)
182{
183 struct qrtr_ctrl_pkt cmsg;
184 int rc;
185
186 LOGD("advertising removal of server [%d:%x]@[%d:%d]\n",
187 srv->service, srv->instance, srv->node, srv->port);
188
189 cmsg.cmd = cpu_to_le32(QRTR_TYPE_DEL_SERVER);
190 cmsg.server.service = cpu_to_le32(srv->service);
191 cmsg.server.instance = cpu_to_le32(srv->instance);
192 cmsg.server.node = cpu_to_le32(srv->node);
193 cmsg.server.port = cpu_to_le32(srv->port);
194
195 rc = sendto(ctx->sock, &cmsg, sizeof(cmsg), 0,
196 (struct sockaddr *)dest, sizeof(*dest));
197 if (rc < 0)
198 PLOGW("sendto()");
199
200 return rc;
201}
202
203static int lookup_notify(struct context *ctx, struct sockaddr_qrtr *to,
204 struct server *srv, bool new)
205{
206 struct qrtr_ctrl_pkt pkt = {};
207 int rc;
208
209 pkt.cmd = new ? QRTR_TYPE_NEW_SERVER : QRTR_TYPE_DEL_SERVER;
210 if (srv) {
211 pkt.server.service = cpu_to_le32(srv->service);
212 pkt.server.instance = cpu_to_le32(srv->instance);
213 pkt.server.node = cpu_to_le32(srv->node);
214 pkt.server.port = cpu_to_le32(srv->port);
215 }
216
217 rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
218 (struct sockaddr *)to, sizeof(*to));
219 if (rc < 0)
220 PLOGW("send lookup result failed");
221 return rc;
222}
223
224static int annouce_servers(struct context *ctx, struct sockaddr_qrtr *sq)
225{
226 struct map_entry *me;
227 struct server *srv;
228 struct node *node;
229 int rc;
230
231 node = node_get(ctx->local_node);
232 if (!node)
233 return 0;
234
235 map_for_each(&node->services, me) {
236 srv = map_iter_data(me, struct server, mi);
237
238 rc = service_announce_new(ctx, sq, srv);
239 if (rc < 0)
240 return rc;
241 }
242
243 return 0;
244}
245
246static struct server *server_add(unsigned int service, unsigned int instance,
247 unsigned int node_id, unsigned int port)
248{
249 struct map_item *mi;
250 struct server *srv;
251 struct node *node;
252 int rc;
253
254 if (!service || !port)
255 return NULL;
256
257 srv = calloc(1, sizeof(*srv));
258 if (srv == NULL)
259 return NULL;
260
261 srv->service = service;
262 srv->instance = instance;
263 srv->node = node_id;
264 srv->port = port;
265
266 node = node_get(node_id);
267 if (!node)
268 goto err;
269
270 rc = map_reput(&node->services, hash_u32(port), &srv->mi, &mi);
271 if (rc)
272 goto err;
273
274 LOGD("add server [%d:%x]@[%d:%d]\n", srv->service, srv->instance,
275 srv->node, srv->port);
276
277 if (mi) { /* we replaced someone */
278 struct server *old = container_of(mi, struct server, mi);
279 free(old);
280 }
281
282 return srv;
283
284err:
285 free(srv);
286 return NULL;
287}
288
289static int server_del(struct context *ctx, struct node *node, unsigned int port)
290{
291 struct lookup *lookup;
292 struct list_item *li;
293 struct map_item *mi;
294 struct server *srv;
295
296 mi = map_get(&node->services, hash_u32(port));
297 if (!mi)
298 return -ENOENT;
299
300 srv = container_of(mi, struct server, mi);
301 map_remove(&node->services, srv->mi.key);
302
303 /* Broadcast the removal of local services */
304 if (srv->node == ctx->local_node)
305 service_announce_del(ctx, &ctx->bcast_sq, srv);
306
307 /* Announce the service's disappearance to observers */
308 list_for_each(&ctx->lookups, li) {
309 lookup = container_of(li, struct lookup, li);
310 if (lookup->service && lookup->service != srv->service)
311 continue;
312 if (lookup->instance && lookup->instance != srv->instance)
313 continue;
314
315 lookup_notify(ctx, &lookup->sq, srv, false);
316 }
317
318 free(srv);
319
320 return 0;
321}
322
323static int ctrl_cmd_hello(struct context *ctx, struct sockaddr_qrtr *sq,
324 const void *buf, size_t len)
325{
326 int rc;
327
328 rc = sendto(ctx->sock, buf, len, 0, (void *)sq, sizeof(*sq));
329 if (rc > 0)
330 rc = annouce_servers(ctx, sq);
331
332 return rc;
333}
334
335static int ctrl_cmd_bye(struct context *ctx, struct sockaddr_qrtr *from)
336{
337 struct qrtr_ctrl_pkt pkt;
338 struct sockaddr_qrtr sq;
339 struct node *local_node;
340 struct map_entry *me;
341 struct server *srv;
342 struct node *node;
343 int rc;
344
345 node = node_get(from->sq_node);
346 if (!node)
347 return 0;
348
349 map_for_each(&node->services, me) {
350 srv = map_iter_data(me, struct server, mi);
351
352 server_del(ctx, node, srv->port);
353 }
354
355 /* Advertise the removal of this client to all local services */
356 local_node = node_get(ctx->local_node);
357 if (!local_node)
358 return 0;
359
360 memset(&pkt, 0, sizeof(pkt));
361 pkt.cmd = QRTR_TYPE_BYE;
362 pkt.client.node = from->sq_node;
363
364 map_for_each(&local_node->services, me) {
365 srv = map_iter_data(me, struct server, mi);
366
367 sq.sq_family = AF_QIPCRTR;
368 sq.sq_node = srv->node;
369 sq.sq_port = srv->port;
370
371 rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
372 (struct sockaddr *)&sq, sizeof(sq));
373 if (rc < 0)
374 PLOGW("bye propagation failed");
375 }
376
377 return 0;
378}
379
380static int ctrl_cmd_del_client(struct context *ctx, struct sockaddr_qrtr *from,
381 unsigned node_id, unsigned port)
382{
383 struct qrtr_ctrl_pkt pkt;
384 struct sockaddr_qrtr sq;
385 struct node *local_node;
386 struct list_item *tmp;
387 struct lookup *lookup;
388 struct list_item *li;
389 struct map_entry *me;
390 struct server *srv;
391 struct node *node;
392 int rc;
393
394 /* Don't accept spoofed messages */
395 if (from->sq_node != node_id)
396 return -EINVAL;
397
398 /* Local DEL_CLIENT messages comes from the port being closed */
399 if (from->sq_node == ctx->local_node && from->sq_port != port)
400 return -EINVAL;
401
402 /* Remove any lookups by this client */
403 list_for_each_safe(&ctx->lookups, li, tmp) {
404 lookup = container_of(li, struct lookup, li);
405 if (lookup->sq.sq_node != node_id)
406 continue;
407 if (lookup->sq.sq_port != port)
408 continue;
409
410 list_remove(&ctx->lookups, &lookup->li);
411 free(lookup);
412 }
413
414 /* Remove the server belonging to this port*/
415 node = node_get(node_id);
416 if (node)
417 server_del(ctx, node, port);
418
419 /* Advertise the removal of this client to all local services */
420 local_node = node_get(ctx->local_node);
421 if (!local_node)
422 return 0;
423
424 pkt.cmd = QRTR_TYPE_DEL_CLIENT;
425 pkt.client.node = node_id;
426 pkt.client.port = port;
427
428 map_for_each(&local_node->services, me) {
429 srv = map_iter_data(me, struct server, mi);
430
431 sq.sq_family = AF_QIPCRTR;
432 sq.sq_node = srv->node;
433 sq.sq_port = srv->port;
434
435 rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
436 (struct sockaddr *)&sq, sizeof(sq));
437 if (rc < 0)
438 PLOGW("del_client propagation failed");
439 }
440
441 return 0;
442}
443
444static int ctrl_cmd_new_server(struct context *ctx, struct sockaddr_qrtr *from,
445 unsigned int service, unsigned int instance,
446 unsigned int node_id, unsigned int port)
447{
448 struct lookup *lookup;
449 struct list_item *li;
450 struct server *srv;
451 int rc = 0;
452
453 /* Ignore specified node and port for local servers*/
454 if (from->sq_node == ctx->local_node) {
455 node_id = from->sq_node;
456 port = from->sq_port;
457 }
458
459 /* Don't accept spoofed messages */
460 if (from->sq_node != node_id)
461 return -EINVAL;
462
463 srv = server_add(service, instance, node_id, port);
464 if (!srv)
465 return -EINVAL;
466
467 if (srv->node == ctx->local_node)
468 rc = service_announce_new(ctx, &ctx->bcast_sq, srv);
469
470 list_for_each(&ctx->lookups, li) {
471 lookup = container_of(li, struct lookup, li);
472 if (lookup->service && lookup->service != service)
473 continue;
474 if (lookup->instance && lookup->instance != instance)
475 continue;
476
477 lookup_notify(ctx, &lookup->sq, srv, true);
478 }
479
480 return rc;
481}
482
483static int ctrl_cmd_del_server(struct context *ctx, struct sockaddr_qrtr *from,
John Stultz26656692020-02-26 23:41:07 +0000484 unsigned int service __unused,
485 unsigned int instance __unused,
Amit Pundird477f822020-02-07 22:26:08 +0530486 unsigned int node_id, unsigned int port)
487{
488 struct node *node;
489
490 /* Ignore specified node and port for local servers*/
491 if (from->sq_node == ctx->local_node) {
492 node_id = from->sq_node;
493 port = from->sq_port;
494 }
495
496 /* Don't accept spoofed messages */
497 if (from->sq_node != node_id)
498 return -EINVAL;
499
500 /* Local servers may only unregister themselves */
501 if (from->sq_node == ctx->local_node && from->sq_port != port)
502 return -EINVAL;
503
504 node = node_get(node_id);
505 if (!node)
506 return -ENOENT;
507
508 return server_del(ctx, node, port);
509}
510
511static int ctrl_cmd_new_lookup(struct context *ctx, struct sockaddr_qrtr *from,
512 unsigned int service, unsigned int instance)
513{
514 struct server_filter filter;
515 struct list reply_list;
516 struct lookup *lookup;
517 struct list_item *li;
518 struct server *srv;
519
520 /* Accept only local observers */
521 if (from->sq_node != ctx->local_node)
522 return -EINVAL;
523
524 lookup = calloc(1, sizeof(*lookup));
525 if (!lookup)
526 return -EINVAL;
527
528 lookup->sq = *from;
529 lookup->service = service;
530 lookup->instance = instance;
531 list_append(&ctx->lookups, &lookup->li);
532
533 memset(&filter, 0, sizeof(filter));
534 filter.service = service;
535 filter.instance = instance;
536
537 server_query(&filter, &reply_list);
538 list_for_each(&reply_list, li) {
539 srv = container_of(li, struct server, qli);
540
541 lookup_notify(ctx, from, srv, true);
542 }
543
544 lookup_notify(ctx, from, NULL, true);
545
546 return 0;
547}
548
549static int ctrl_cmd_del_lookup(struct context *ctx, struct sockaddr_qrtr *from,
550 unsigned int service, unsigned int instance)
551{
552 struct lookup *lookup;
553 struct list_item *tmp;
554 struct list_item *li;
555
556 list_for_each_safe(&ctx->lookups, li, tmp) {
557 lookup = container_of(li, struct lookup, li);
558 if (lookup->sq.sq_node != from->sq_node)
559 continue;
560 if (lookup->sq.sq_port != from->sq_port)
561 continue;
562 if (lookup->service != service)
563 continue;
564 if (lookup->instance && lookup->instance != instance)
565 continue;
566
567 list_remove(&ctx->lookups, &lookup->li);
568 free(lookup);
569 }
570
571 return 0;
572}
573
574static void ctrl_port_fn(void *vcontext, struct waiter_ticket *tkt)
575{
576 struct context *ctx = vcontext;
577 struct sockaddr_qrtr sq;
578 int sock = ctx->sock;
579 struct qrtr_ctrl_pkt *msg;
580 unsigned int cmd;
581 char buf[4096];
582 socklen_t sl;
583 ssize_t len;
584 int rc;
585
586 sl = sizeof(sq);
587 len = recvfrom(sock, buf, sizeof(buf), 0, (void *)&sq, &sl);
588 if (len <= 0) {
589 PLOGW("recvfrom()");
590 close(sock);
591 ctx->sock = -1;
592 goto out;
593 }
594 msg = (void *)buf;
595
596 if (len < 4) {
597 LOGW("short packet from %d:%d", sq.sq_node, sq.sq_port);
598 goto out;
599 }
600
601 cmd = le32_to_cpu(msg->cmd);
602 if (cmd < ARRAY_SIZE(ctrl_pkt_strings) && ctrl_pkt_strings[cmd])
603 LOGD("%s from %d:%d\n", ctrl_pkt_strings[cmd], sq.sq_node, sq.sq_port);
604 else
605 LOGD("UNK (%08x) from %d:%d\n", cmd, sq.sq_node, sq.sq_port);
606
607 rc = 0;
608 switch (cmd) {
609 case QRTR_TYPE_HELLO:
610 rc = ctrl_cmd_hello(ctx, &sq, buf, len);
611 break;
612 case QRTR_TYPE_BYE:
613 rc = ctrl_cmd_bye(ctx, &sq);
614 break;
615 case QRTR_TYPE_DEL_CLIENT:
616 rc = ctrl_cmd_del_client(ctx, &sq,
617 le32_to_cpu(msg->client.node),
618 le32_to_cpu(msg->client.port));
619 break;
620 case QRTR_TYPE_NEW_SERVER:
621 rc = ctrl_cmd_new_server(ctx, &sq,
622 le32_to_cpu(msg->server.service),
623 le32_to_cpu(msg->server.instance),
624 le32_to_cpu(msg->server.node),
625 le32_to_cpu(msg->server.port));
626 break;
627 case QRTR_TYPE_DEL_SERVER:
628 rc = ctrl_cmd_del_server(ctx, &sq,
629 le32_to_cpu(msg->server.service),
630 le32_to_cpu(msg->server.instance),
631 le32_to_cpu(msg->server.node),
632 le32_to_cpu(msg->server.port));
633 break;
634 case QRTR_TYPE_EXIT:
635 case QRTR_TYPE_PING:
636 case QRTR_TYPE_RESUME_TX:
637 break;
638 case QRTR_TYPE_NEW_LOOKUP:
639 rc = ctrl_cmd_new_lookup(ctx, &sq,
640 le32_to_cpu(msg->server.service),
641 le32_to_cpu(msg->server.instance));
642 break;
643 case QRTR_TYPE_DEL_LOOKUP:
644 rc = ctrl_cmd_del_lookup(ctx, &sq,
645 le32_to_cpu(msg->server.service),
646 le32_to_cpu(msg->server.instance));
647 break;
648 }
649
650 if (rc < 0)
651 LOGW("failed while handling packet from %d:%d",
652 sq.sq_node, sq.sq_port);
653out:
654 waiter_ticket_clear(tkt);
655}
656
657static int say_hello(struct context *ctx)
658{
659 struct qrtr_ctrl_pkt pkt;
660 int rc;
661
662 memset(&pkt, 0, sizeof(pkt));
663 pkt.cmd = cpu_to_le32(QRTR_TYPE_HELLO);
664
665 rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
666 (struct sockaddr *)&ctx->bcast_sq, sizeof(ctx->bcast_sq));
667 if (rc < 0)
668 return rc;
669
670 return 0;
671}
672
673static void server_mi_free(struct map_item *mi)
674{
675 free(container_of(mi, struct server, mi));
676}
677
678static void node_mi_free(struct map_item *mi)
679{
680 struct node *node = container_of(mi, struct node, mi);
681
682 map_clear(&node->services, server_mi_free);
683 map_destroy(&node->services);
684
685 free(node);
686}
687
Bjorn Anderssonbf70b7d2020-03-01 18:02:13 -0800688static void go_dormant(int sock)
689{
690 close(sock);
691
692 for (;;)
693 sleep(UINT_MAX);
694}
695
Amit Pundird477f822020-02-07 22:26:08 +0530696static void usage(const char *progname)
697{
698 fprintf(stderr, "%s [-f] [-s] [<node-id>]\n", progname);
699 exit(1);
700}
701
702int main(int argc, char **argv)
703{
704 struct waiter_ticket *tkt;
705 struct sockaddr_qrtr sq;
706 struct context ctx;
707 unsigned long addr = (unsigned long)-1;
708 struct waiter *w;
709 socklen_t sl = sizeof(sq);
710 bool foreground = false;
711 bool use_syslog = false;
712 bool verbose_log = false;
713 char *ep;
714 int opt;
715 int rc;
716 const char *progname = basename(argv[0]);
717
718 while ((opt = getopt(argc, argv, "fsv")) != -1) {
719 switch (opt) {
720 case 'f':
721 foreground = true;
722 break;
723 case 's':
724 use_syslog = true;
725 break;
726 case 'v':
727 verbose_log = true;
728 break;
729 default:
730 usage(progname);
731 }
732 }
733
734 qlog_setup(progname, use_syslog);
735 if (verbose_log)
736 qlog_set_min_priority(LOG_DEBUG);
737
738 if (optind < argc) {
739 addr = strtoul(argv[optind], &ep, 10);
740 if (argv[1][0] == '\0' || *ep != '\0' || addr >= UINT_MAX)
741 usage(progname);
742
743 qrtr_set_address(addr);
744 optind++;
745 }
746
747 if (optind != argc)
748 usage(progname);
749
750 w = waiter_create();
751 if (w == NULL)
752 LOGE_AND_EXIT("unable to create waiter");
753
754 list_init(&ctx.lookups);
755
756 rc = map_create(&nodes);
757 if (rc)
758 LOGE_AND_EXIT("unable to create node map");
759
760 ctx.sock = socket(AF_QIPCRTR, SOCK_DGRAM, 0);
761 if (ctx.sock < 0)
762 PLOGE_AND_EXIT("unable to create control socket");
763
764 rc = getsockname(ctx.sock, (void*)&sq, &sl);
765 if (rc < 0)
766 PLOGE_AND_EXIT("getsockname()");
767 sq.sq_port = QRTR_PORT_CTRL;
768 ctx.local_node = sq.sq_node;
769
770 rc = bind(ctx.sock, (void *)&sq, sizeof(sq));
Bjorn Anderssonbf70b7d2020-03-01 18:02:13 -0800771 if (rc < 0) {
772 if (errno == EADDRINUSE) {
773 PLOGE("nameserver already running, going dormant");
774 go_dormant(ctx.sock);
775 }
776
Amit Pundird477f822020-02-07 22:26:08 +0530777 PLOGE_AND_EXIT("bind control socket");
Bjorn Anderssonbf70b7d2020-03-01 18:02:13 -0800778 }
Amit Pundird477f822020-02-07 22:26:08 +0530779
780 ctx.bcast_sq.sq_family = AF_QIPCRTR;
781 ctx.bcast_sq.sq_node = QRTR_NODE_BCAST;
782 ctx.bcast_sq.sq_port = QRTR_PORT_CTRL;
783
784 rc = say_hello(&ctx);
785 if (rc)
786 PLOGE_AND_EXIT("unable to say hello");
787
788 /* If we're going to background, fork and exit parent */
789 if (!foreground && fork() != 0) {
790 close(ctx.sock);
791 exit(0);
792 }
793
794 tkt = waiter_add_fd(w, ctx.sock);
795 waiter_ticket_callback(tkt, ctrl_port_fn, &ctx);
796
797 while (ctx.sock >= 0)
798 waiter_wait(w);
799
800 puts("exiting cleanly");
801
802 waiter_destroy(w);
803
804 map_clear(&nodes, node_mi_free);
805 map_destroy(&nodes);
806
807 return 0;
808}