aboutsummaryrefslogtreecommitdiff
path: root/src/service/testing/gnunet-cmds-helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/service/testing/gnunet-cmds-helper.c')
-rw-r--r--src/service/testing/gnunet-cmds-helper.c713
1 files changed, 713 insertions, 0 deletions
diff --git a/src/service/testing/gnunet-cmds-helper.c b/src/service/testing/gnunet-cmds-helper.c
new file mode 100644
index 000000000..4428d4402
--- /dev/null
+++ b/src/service/testing/gnunet-cmds-helper.c
@@ -0,0 +1,713 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2021 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file testbed/gnunet-cmds-helper.c
23 * @brief Helper binary that is started from a remote interpreter loop to start
24 * a local interpreter loop.
25 *
26 * This helper monitors for three termination events. They are: (1)The
27 * stdin of the helper is closed for reading; (2)the helper received
28 * SIGTERM/SIGINT; (3)the local loop crashed. In case of events 1 and 2
29 * the helper kills the interpreter loop. When the interpreter loop
30 * crashed (event 3), the helper should send a SIGTERM to its own process
31 * group; this behaviour will help terminate any child processes the loop
32 * has started and prevents them from leaking and running forever.
33 *
34 * @author t3sserakt
35 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
36 */
37
38
39#include "platform.h"
40#include "gnunet_util_lib.h"
41#include "gnunet_testing_lib.h"
42#include "gnunet_testing_ng_lib.h"
43#include "gnunet_testing_plugin.h"
44#include "gnunet_testing_netjail_lib.h"
45#include "testing.h"
46#include "testing_cmds.h"
47#include "gnunet_testing_plugin.h"
48#include "gnunet_testing_barrier.h"
49#include <zlib.h>
50
51
52/**
53 * Generic logging shortcut
54 */
55#define LOG(kind, ...) GNUNET_log (kind, __VA_ARGS__)
56
57/**
58 * Debug logging shorthand
59 */
60#define LOG_DEBUG(...) LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
61
62#define NODE_BASE_IP "192.168.15."
63
64#define KNOWN_BASE_IP "92.68.151."
65
66#define ROUTER_BASE_IP "92.68.150."
67
68/* Use the IP addresses below instead of the public ones,
69 * if the start script was not started from within a new namespace
70 * created by unshare. The UPNP test case needs public IP
71 * addresse for miniupnpd to function.
72 * FIXME We should introduce a switch indicating if public
73 * addresses should be used or not. This info has to be
74 * propagated from the start script to the c code.
75#define KNOWN_BASE_IP "172.16.151."
76
77#define ROUTER_BASE_IP "172.16.150."
78*/
79
80struct GNUNET_SCHEDULER_Task *finished_task;
81
82struct GNUNET_TESTING_Interpreter *is;
83
84/**
85 * Struct with information about a specific node and the whole network namespace setup.
86 *
87 */
88struct NodeIdentifier
89{
90 /**
91 * The number of the namespace this node is in.
92 *
93 */
94 char *n;
95
96 /**
97 * The number of the node in the namespace.
98 *
99 */
100 char *m;
101
102 /**
103 * The number of namespaces
104 *
105 */
106 char *global_n;
107
108 /**
109 * The number of local nodes per namespace.
110 *
111 */
112 char *local_m;
113
114 /**
115 * Shall we read the topology from file, or from a string.
116 */
117 unsigned int *read_file;
118
119 /**
120 * String with topology data or name of topology file.
121 */
122 char *topology_data;
123};
124
125/**
126 * Context for a single write on a chunk of memory
127 */
128struct WriteContext
129{
130 /**
131 * The data to write
132 */
133 void *data;
134
135 /**
136 * The length of the data
137 */
138 size_t length;
139
140 /**
141 * The current position from where the write operation should begin
142 */
143 size_t pos;
144};
145
146/**
147 * The process handle to the testbed service
148
149static struct GNUNET_OS_Process *cmd_binary_process;*/
150
151/**
152 * Plugin to dynamically load a test case.
153 */
154struct TestcasePlugin *plugin;
155
156/**
157 * Our message stream tokenizer
158 */
159struct GNUNET_MessageStreamTokenizer *tokenizer;
160
161/**
162 * Disk handle from stdin
163 */
164static struct GNUNET_DISK_FileHandle *stdin_fd;
165
166/**
167 * Disk handle for stdout
168 */
169static struct GNUNET_DISK_FileHandle *stdout_fd;
170
171/**
172 * Pipe used to communicate shutdown via signal.
173 */
174static struct GNUNET_DISK_PipeHandle *sigpipe;
175
176/**
177 * Task identifier for the read task
178 */
179static struct GNUNET_SCHEDULER_Task *read_task_id;
180
181/**
182 * Task identifier for the write task
183 */
184static struct GNUNET_SCHEDULER_Task *write_task_id;
185
186/**
187 * Are we done reading messages from stdin?
188 */
189static int done_reading;
190
191/**
192 * Result to return in case we fail
193 */
194static int status;
195
196
197/**
198 * Task to shut down cleanly
199 *
200 * @param cls NULL
201 */
202static void
203do_shutdown (void *cls)
204{
205
206 LOG_DEBUG ("Shutting down.\n");
207
208 if (NULL != read_task_id)
209 {
210 GNUNET_SCHEDULER_cancel (read_task_id);
211 read_task_id = NULL;
212 }
213 if (NULL != write_task_id)
214 {
215 struct WriteContext *wc;
216
217 wc = GNUNET_SCHEDULER_cancel (write_task_id);
218 write_task_id = NULL;
219 GNUNET_free (wc->data);
220 GNUNET_free (wc);
221 }
222 if (NULL != stdin_fd)
223 (void) GNUNET_DISK_file_close (stdin_fd);
224 if (NULL != stdout_fd)
225 (void) GNUNET_DISK_file_close (stdout_fd);
226 GNUNET_MST_destroy (tokenizer);
227 tokenizer = NULL;
228 GNUNET_PLUGIN_unload (plugin->library_name,
229 NULL);
230 GNUNET_free (plugin);
231}
232
233
234/**
235 * Task to write to the standard out
236 *
237 * @param cls the WriteContext
238 */
239static void
240write_task (void *cls)
241{
242 struct WriteContext *wc = cls;
243 ssize_t bytes_wrote;
244
245 GNUNET_assert (NULL != wc);
246 write_task_id = NULL;
247 bytes_wrote = GNUNET_DISK_file_write (stdout_fd,
248 wc->data + wc->pos,
249 wc->length - wc->pos);
250 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
251 "message send to master loop\n");
252 if (GNUNET_SYSERR == bytes_wrote)
253 {
254 LOG (GNUNET_ERROR_TYPE_WARNING,
255 "Cannot reply back successful initialization\n");
256 GNUNET_free (wc->data);
257 GNUNET_free (wc);
258 return;
259 }
260 wc->pos += bytes_wrote;
261 if (wc->pos == wc->length)
262 {
263 GNUNET_free (wc->data);
264 GNUNET_free (wc);
265 return;
266 }
267 write_task_id = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
268 stdout_fd,
269 &write_task,
270 wc);
271}
272
273
274/**
275 * Callback to write a message to the master loop.
276 *
277 */
278static void
279write_message (struct GNUNET_MessageHeader *message,
280 size_t msg_length)
281{
282 struct WriteContext *wc;
283
284 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285 "write message to master loop\n");
286 wc = GNUNET_new (struct WriteContext);
287 wc->length = msg_length;
288 wc->data = message;
289 write_task_id = GNUNET_SCHEDULER_add_write_file (
290 GNUNET_TIME_UNIT_FOREVER_REL,
291 stdout_fd,
292 &write_task,
293 wc);
294}
295
296
297static void
298delay_shutdown_cb ()
299{
300 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
301 "doing shutdown after delay\n");
302 GNUNET_SCHEDULER_shutdown ();
303}
304
305
306struct GNUNET_MessageHeader *
307GNUNET_TESTING_send_local_test_finished_msg ()
308{
309 struct GNUNET_TESTING_CommandLocalFinished *reply;
310 size_t msg_length;
311
312 msg_length = sizeof(struct GNUNET_TESTING_CommandLocalFinished);
313 reply = GNUNET_new (struct GNUNET_TESTING_CommandLocalFinished);
314 reply->header.type = htons (GNUNET_MESSAGE_TYPE_CMDS_HELPER_LOCAL_FINISHED);
315 reply->header.size = htons ((uint16_t) msg_length);
316
317 return (struct GNUNET_MessageHeader *) reply;
318}
319
320
321static void
322finished_cb (enum GNUNET_GenericReturnValue rv)
323{
324 struct GNUNET_TESTING_CommandLocalFinished *reply;
325 size_t msg_length;
326
327 msg_length = sizeof(struct GNUNET_TESTING_CommandLocalFinished);
328 reply = GNUNET_new (struct GNUNET_TESTING_CommandLocalFinished);
329 reply->header.type = htons (GNUNET_MESSAGE_TYPE_CMDS_HELPER_LOCAL_FINISHED);
330 reply->header.size = htons ((uint16_t) msg_length);
331 reply->rv = rv;
332
333 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
334 "message prepared\n");
335 write_message ((struct GNUNET_MessageHeader *) reply, msg_length);
336 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
337 "message send\n");
338 // FIXME: bad hack, do not write 1s, have continuation after write_message() is done!
339 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
340 "delaying shutdown\n");
341 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
342 &delay_shutdown_cb,
343 NULL);
344}
345
346
347/**
348 * Functions with this signature are called whenever a
349 * complete message is received by the tokenizer.
350 *
351 * Do not call #GNUNET_mst_destroy() in this callback
352 *
353 * @param cls identification of the client
354 * @param message the actual message
355 * @return #GNUNET_OK on success,
356 * #GNUNET_NO to stop further processing (no error)
357 * #GNUNET_SYSERR to stop further processing with error
358 */
359static enum GNUNET_GenericReturnValue
360tokenizer_cb (void *cls,
361 const struct GNUNET_MessageHeader *message)
362{
363 struct NodeIdentifier *ni = cls;
364 const struct GNUNET_TESTING_CommandHelperInit *msg;
365 struct GNUNET_TESTING_CommandHelperReply *reply;
366 char *binary;
367 char *plugin_name;
368 size_t plugin_name_size;
369 uint16_t msize;
370 uint16_t type;
371 size_t msg_length;
372 char *router_ip;
373 char *node_ip;
374 unsigned int namespace_n;
375
376 type = ntohs (message->type);
377 msize = ntohs (message->size);
378 LOG (GNUNET_ERROR_TYPE_ERROR,
379 "Received message type %u and size %u\n",
380 type,
381 msize);
382 switch (type)
383 {
384 case GNUNET_MESSAGE_TYPE_CMDS_HELPER_INIT:
385 {
386 msg = (const struct GNUNET_TESTING_CommandHelperInit *) message;
387 plugin_name_size = ntohs (msg->plugin_name_size);
388 if ((sizeof(struct GNUNET_TESTING_CommandHelperInit) + plugin_name_size) >
389 msize)
390 {
391 GNUNET_break (0);
392 LOG (GNUNET_ERROR_TYPE_WARNING,
393 "Received unexpected message -- exiting\n");
394 goto error;
395 }
396 plugin_name = GNUNET_malloc (plugin_name_size + 1);
397 GNUNET_strlcpy (plugin_name,
398 ((char *) &msg[1]),
399 plugin_name_size + 1);
400
401 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-cmd");
402
403 plugin = GNUNET_new (struct TestcasePlugin);
404 plugin->api = GNUNET_PLUGIN_load (plugin_name,
405 NULL);
406 plugin->library_name = GNUNET_strdup (basename (plugin_name));
407
408 plugin->global_n = ni->global_n;
409 plugin->local_m = ni->local_m;
410 plugin->n = ni->n;
411 plugin->m = ni->m;
412
413 GNUNET_asprintf (&router_ip,
414 ROUTER_BASE_IP "%s",
415 plugin->n);
416 {
417 char dummy;
418
419 if (1 !=
420 sscanf (plugin->n,
421 "%u%c",
422 &namespace_n,
423 &dummy))
424 {
425 // FIXME: how to handle error nicely?
426 GNUNET_break (0);
427 namespace_n = 0;
428 }
429 }
430
431 if (0 == namespace_n)
432 {
433 LOG (GNUNET_ERROR_TYPE_DEBUG,
434 "known node n: %s\n",
435 plugin->n);
436 GNUNET_asprintf (&node_ip,
437 KNOWN_BASE_IP "%s",
438 plugin->m);
439 }
440 else
441 {
442 LOG (GNUNET_ERROR_TYPE_DEBUG,
443 "subnet node n: %s\n",
444 plugin->n);
445 GNUNET_asprintf (&node_ip,
446 NODE_BASE_IP "%s",
447 plugin->m);
448 }
449
450 is = plugin->api->start_testcase (&write_message,
451 router_ip,
452 node_ip,
453 plugin->m,
454 plugin->n,
455 plugin->local_m,
456 ni->topology_data,
457 ni->read_file,
458 &finished_cb);
459 GNUNET_free (node_ip);
460 GNUNET_free (binary);
461 GNUNET_free (router_ip);
462 GNUNET_free (plugin_name);
463
464 msg_length = sizeof(struct GNUNET_TESTING_CommandHelperReply);
465 reply = GNUNET_new (struct GNUNET_TESTING_CommandHelperReply);
466 reply->header.type = htons (GNUNET_MESSAGE_TYPE_CMDS_HELPER_REPLY);
467 reply->header.size = htons ((uint16_t) msg_length);
468 write_message (&reply->header,
469 msg_length);
470 return GNUNET_OK;
471 }
472 case GNUNET_MESSAGE_TYPE_CMDS_HELPER_BARRIER_CROSSABLE:
473 {
474 const char *barrier_name;
475 struct CommandBarrierCrossable *adm = (struct
476 CommandBarrierCrossable *) message;
477
478 barrier_name = (const char *) &adm[1];
479 LOG (GNUNET_ERROR_TYPE_DEBUG,
480 "cross barrier %s\n",
481 barrier_name);
482 TST_interpreter_finish_attached_cmds (is,
483 barrier_name);
484 return GNUNET_OK;
485 }
486 case GNUNET_MESSAGE_TYPE_CMDS_HELPER_ALL_PEERS_STARTED:
487 {
488 LOG (GNUNET_ERROR_TYPE_DEBUG,
489 "all peers started\n");
490 plugin->api->all_peers_started ();
491 return GNUNET_OK;
492 }
493 case GNUNET_MESSAGE_TYPE_CMDS_HELPER_ALL_LOCAL_TESTS_PREPARED:
494 {
495 LOG (GNUNET_ERROR_TYPE_DEBUG,
496 "all local tests prepared\n");
497 plugin->api->all_local_tests_prepared ();
498 return GNUNET_OK;
499 }
500 default:
501 LOG (GNUNET_ERROR_TYPE_WARNING, "Received unexpected message -- exiting\n");
502 goto error;
503 }
504
505error:
506 status = GNUNET_SYSERR;
507 LOG (GNUNET_ERROR_TYPE_ERROR,
508 "tokenizer shutting down!\n");
509 GNUNET_SCHEDULER_shutdown ();
510 return GNUNET_SYSERR;
511}
512
513
514/**
515 * Task to read from stdin
516 *
517 * @param cls NULL
518 */
519static void
520read_task (void *cls)
521{
522 char buf[GNUNET_MAX_MESSAGE_SIZE];
523 ssize_t sread;
524
525 read_task_id = NULL;
526 sread = GNUNET_DISK_file_read (stdin_fd, buf, sizeof(buf));
527 if ((GNUNET_SYSERR == sread) || (0 == sread))
528 {
529 LOG_DEBUG ("STDIN closed\n");
530 GNUNET_SCHEDULER_shutdown ();
531 return;
532 }
533 if (GNUNET_YES == done_reading)
534 {
535 /* didn't expect any more data! */
536 GNUNET_break_op (0);
537 LOG (GNUNET_ERROR_TYPE_ERROR,
538 "tokenizer shutting down during reading, didn't expect any more data!\n");
539 GNUNET_SCHEDULER_shutdown ();
540 return;
541 }
542 LOG_DEBUG ("Read %u bytes\n", (unsigned int) sread);
543 /* FIXME: could introduce a GNUNET_MST_read2 to read
544 directly from 'stdin_fd' and save a memcpy() here */
545 if (GNUNET_OK !=
546 GNUNET_MST_from_buffer (tokenizer, buf, sread, GNUNET_NO, GNUNET_NO))
547 {
548 GNUNET_break (0);
549 LOG (GNUNET_ERROR_TYPE_ERROR,
550 "tokenizer shutting down during reading, writing to buffer failed!\n");
551 GNUNET_SCHEDULER_shutdown ();
552 return;
553 }
554 read_task_id /* No timeout while reading */
555 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
556 stdin_fd,
557 &read_task,
558 NULL);
559}
560
561
562/**
563 * Main function that will be run.
564 *
565 * @param cls closure
566 * @param args remaining command-line arguments
567 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
568 * @param cfg configuration
569 */
570static void
571run (void *cls,
572 char *const *args,
573 const char *cfgfile,
574 const struct GNUNET_CONFIGURATION_Handle *cfg)
575{
576 struct NodeIdentifier *ni = cls;
577
578 LOG (GNUNET_ERROR_TYPE_DEBUG,
579 "Starting interpreter loop helper...\n");
580
581 tokenizer = GNUNET_MST_create (&tokenizer_cb,
582 ni);
583 stdin_fd = GNUNET_DISK_get_handle_from_native (stdin);
584 stdout_fd = GNUNET_DISK_get_handle_from_native (stdout);
585 read_task_id = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
586 stdin_fd,
587 &read_task,
588 NULL);
589 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
590 NULL);
591 LOG (GNUNET_ERROR_TYPE_DEBUG,
592 "Interpreter loop helper started.\n");
593}
594
595
596/**
597 * Signal handler called for SIGCHLD.
598 */
599static void
600sighandler_child_death ()
601{
602 static char c;
603 int old_errno; /* back-up errno */
604
605 old_errno = errno;
606 GNUNET_break (
607 1 ==
608 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle (sigpipe,
609 GNUNET_DISK_PIPE_END_WRITE),
610 &c,
611 sizeof(c)));
612 errno = old_errno;
613}
614
615
616/**
617 * Main function
618 *
619 * @param argc the number of command line arguments
620 * @param argv command line arg array
621 * @return return code
622 */
623int
624main (int argc, char **argv)
625{
626 struct NodeIdentifier *ni;
627 struct GNUNET_SIGNAL_Context *shc_chld;
628 struct GNUNET_GETOPT_CommandLineOption options[] = {
629 GNUNET_GETOPT_OPTION_END
630 };
631 int ret;
632 unsigned int sscanf_ret;
633 int i;
634 size_t topology_data_length = 0;
635 unsigned int read_file;
636 char cr[2] = "\n\0";
637
638 GNUNET_log_setup ("gnunet-cmds-helper",
639 "DEBUG",
640 NULL);
641 ni = GNUNET_new (struct NodeIdentifier);
642 ni->global_n = argv[1];
643 ni->local_m = argv[2];
644 ni->m = argv[3];
645 ni->n = argv[4];
646
647 errno = 0;
648 sscanf_ret = sscanf (argv[5], "%u", &read_file);
649
650 if (errno != 0)
651 {
652 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "sscanf");
653 }
654 else if (1 == read_file)
655 ni->topology_data = argv[6];
656 else if (0 == read_file)
657 {
658 for (i = 6; i<argc; i++)
659 topology_data_length += strlen (argv[i]) + 1;
660 LOG (GNUNET_ERROR_TYPE_DEBUG,
661 "topo data length %lu\n",
662 topology_data_length);
663 ni->topology_data = GNUNET_malloc (topology_data_length);
664 memset (ni->topology_data, '\0', topology_data_length);
665 for (i = 6; i<argc; i++)
666 {
667 strcat (ni->topology_data, argv[i]);
668 strcat (ni->topology_data, cr);
669 }
670 }
671 else
672 {
673 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
674 "Wrong input for the fourth argument\n");
675 }
676 GNUNET_assert (0 < sscanf_ret);
677 ni->read_file = &read_file;
678 ni->topology_data[topology_data_length - 1] = '\0';
679 LOG (GNUNET_ERROR_TYPE_DEBUG,
680 "topo data %s\n",
681 ni->topology_data);
682
683 status = GNUNET_OK;
684 if (NULL ==
685 (sigpipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE)))
686 {
687 GNUNET_break (0);
688 return 1;
689 }
690 shc_chld =
691 GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD,
692 &sighandler_child_death);
693 ret = GNUNET_PROGRAM_run (argc,
694 argv,
695 "gnunet-cmds-helper",
696 "Helper for starting a local interpreter loop",
697 options,
698 &run,
699 ni);
700
701 LOG (GNUNET_ERROR_TYPE_DEBUG,
702 "Finishing helper\n");
703 GNUNET_SIGNAL_handler_uninstall (shc_chld);
704 shc_chld = NULL;
705 GNUNET_DISK_pipe_close (sigpipe);
706 GNUNET_free (ni);
707 if (GNUNET_OK != ret)
708 return 1;
709 return (GNUNET_OK == status) ? 0 : 1;
710}
711
712
713/* end of gnunet-cmds-helper.c */