aboutsummaryrefslogtreecommitdiff
path: root/src/testbed/gnunet-helper-testbed.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testbed/gnunet-helper-testbed.c')
-rw-r--r--src/testbed/gnunet-helper-testbed.c613
1 files changed, 0 insertions, 613 deletions
diff --git a/src/testbed/gnunet-helper-testbed.c b/src/testbed/gnunet-helper-testbed.c
deleted file mode 100644
index 938e50448..000000000
--- a/src/testbed/gnunet-helper-testbed.c
+++ /dev/null
@@ -1,613 +0,0 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2008--2013, 2016 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-helper-testbed.c
23 * @brief Helper binary that is started from a remote controller to start
24 * gnunet-service-testbed. This binary also receives configuration
25 * from the remove controller which is put in a temporary location
26 * with ports and paths fixed so that gnunet-service-testbed runs
27 * without any hurdles.
28 *
29 * This helper monitors for three termination events. They are: (1)The
30 * stdin of the helper is closed for reading; (2)the helper received
31 * SIGTERM/SIGINT; (3)the testbed crashed. In case of events 1 and 2
32 * the helper kills the testbed service. When testbed crashed (event
33 * 3), the helper should send a SIGTERM to its own process group; this
34 * behaviour will help terminate any child processes (peers) testbed
35 * has started and prevents them from leaking and running forever.
36 *
37 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
38 */
39
40
41#include "platform.h"
42#include "gnunet_util_lib.h"
43#include "gnunet_testing_lib.h"
44#include "gnunet_testbed_service.h"
45#include "testbed_helper.h"
46#include "testbed_api.h"
47#include <zlib.h>
48
49/**
50 * Generic logging shortcut
51 */
52#define LOG(kind, ...) GNUNET_log (kind, __VA_ARGS__)
53
54/**
55 * Debug logging shorthand
56 */
57#define LOG_DEBUG(...) LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
58
59
60/**
61 * Context for a single write on a chunk of memory
62 */
63struct WriteContext
64{
65 /**
66 * The data to write
67 */
68 void *data;
69
70 /**
71 * The length of the data
72 */
73 size_t length;
74
75 /**
76 * The current position from where the write operation should begin
77 */
78 size_t pos;
79};
80
81
82/**
83 * Handle to the testing system
84 */
85static struct GNUNET_TESTING_System *test_system;
86
87/**
88 * Our message stream tokenizer
89 */
90struct GNUNET_MessageStreamTokenizer *tokenizer;
91
92/**
93 * Disk handle from stdin
94 */
95static struct GNUNET_DISK_FileHandle *stdin_fd;
96
97/**
98 * Disk handle for stdout
99 */
100static struct GNUNET_DISK_FileHandle *stdout_fd;
101
102/**
103 * The process handle to the testbed service
104 */
105static struct GNUNET_OS_Process *testbed;
106
107/**
108 * Pipe used to communicate shutdown via signal.
109 */
110static struct GNUNET_DISK_PipeHandle *sigpipe;
111
112/**
113 * Task identifier for the read task
114 */
115static struct GNUNET_SCHEDULER_Task *read_task_id;
116
117/**
118 * Task identifier for the write task
119 */
120static struct GNUNET_SCHEDULER_Task *write_task_id;
121
122/**
123 * Task to kill the child
124 */
125static struct GNUNET_SCHEDULER_Task *child_death_task_id;
126
127/**
128 * Are we done reading messages from stdin?
129 */
130static int done_reading;
131
132/**
133 * Result to return in case we fail
134 */
135static int status;
136
137
138/**
139 * Task to shut down cleanly
140 *
141 * @param cls NULL
142 */
143static void
144shutdown_task (void *cls)
145{
146 LOG_DEBUG ("Shutting down\n");
147 if (NULL != testbed)
148 {
149 LOG_DEBUG ("Killing testbed\n");
150 GNUNET_break (0 == GNUNET_OS_process_kill (testbed, GNUNET_TERM_SIG));
151 }
152 if (NULL != read_task_id)
153 {
154 GNUNET_SCHEDULER_cancel (read_task_id);
155 read_task_id = NULL;
156 }
157 if (NULL != write_task_id)
158 {
159 struct WriteContext *wc;
160
161 wc = GNUNET_SCHEDULER_cancel (write_task_id);
162 write_task_id = NULL;
163 GNUNET_free (wc->data);
164 GNUNET_free (wc);
165 }
166 if (NULL != child_death_task_id)
167 {
168 GNUNET_SCHEDULER_cancel (child_death_task_id);
169 child_death_task_id = NULL;
170 }
171 if (NULL != stdin_fd)
172 (void) GNUNET_DISK_file_close (stdin_fd);
173 if (NULL != stdout_fd)
174 (void) GNUNET_DISK_file_close (stdout_fd);
175 GNUNET_MST_destroy (tokenizer);
176 tokenizer = NULL;
177 if (NULL != testbed)
178 {
179 GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (testbed));
180 GNUNET_OS_process_destroy (testbed);
181 testbed = NULL;
182 }
183 if (NULL != test_system)
184 {
185 GNUNET_TESTING_system_destroy (test_system, GNUNET_YES);
186 test_system = NULL;
187 }
188}
189
190
191/**
192 * Task to write to the standard out
193 *
194 * @param cls the WriteContext
195 */
196static void
197write_task (void *cls)
198{
199 struct WriteContext *wc = cls;
200 ssize_t bytes_wrote;
201
202 GNUNET_assert (NULL != wc);
203 write_task_id = NULL;
204 bytes_wrote = GNUNET_DISK_file_write (stdout_fd,
205 wc->data + wc->pos,
206 wc->length - wc->pos);
207 if (GNUNET_SYSERR == bytes_wrote)
208 {
209 LOG (GNUNET_ERROR_TYPE_WARNING, "Cannot reply back configuration\n");
210 GNUNET_free (wc->data);
211 GNUNET_free (wc);
212 return;
213 }
214 wc->pos += bytes_wrote;
215 if (wc->pos == wc->length)
216 {
217 GNUNET_free (wc->data);
218 GNUNET_free (wc);
219 return;
220 }
221 write_task_id = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
222 stdout_fd,
223 &write_task,
224 wc);
225}
226
227
228/**
229 * Task triggered whenever we receive a SIGCHLD (child
230 * process died).
231 *
232 * @param cls closure, NULL if we need to self-restart
233 */
234static void
235child_death_task (void *cls)
236{
237 const struct GNUNET_DISK_FileHandle *pr;
238 char c[16];
239 enum GNUNET_OS_ProcessStatusType type;
240 unsigned long code;
241 int ret;
242
243 pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
244 child_death_task_id = NULL;
245 /* consume the signal */
246 GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof(c)));
247 LOG_DEBUG ("Got SIGCHLD\n");
248 if (NULL == testbed)
249 {
250 GNUNET_break (0);
251 return;
252 }
253 GNUNET_break (GNUNET_SYSERR !=
254 (ret = GNUNET_OS_process_status (testbed, &type, &code)));
255 if (GNUNET_NO != ret)
256 {
257 GNUNET_OS_process_destroy (testbed);
258 testbed = NULL;
259 /* Send SIGTERM to our process group */
260 if (0 != kill (0, GNUNET_TERM_SIG))
261 {
262 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "signal");
263 GNUNET_SCHEDULER_shutdown (); /* Couldn't send the signal, we shutdown frowning */
264 }
265 return;
266 }
267 LOG_DEBUG ("Child hasn't died. Resuming to monitor its status\n");
268 child_death_task_id =
269 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
270 pr,
271 &child_death_task,
272 NULL);
273}
274
275
276/**
277 * Functions with this signature are called whenever a
278 * complete message is received by the tokenizer.
279 *
280 * Do not call #GNUNET_mst_destroy() in this callback
281 *
282 * @param cls identification of the client
283 * @param message the actual message
284 * @return #GNUNET_OK on success,
285 * #GNUNET_NO to stop further processing (no error)
286 * #GNUNET_SYSERR to stop further processing with error
287 */
288static int
289tokenizer_cb (void *cls, const struct GNUNET_MessageHeader *message)
290{
291 const struct GNUNET_TESTBED_HelperInit *msg;
292 struct GNUNET_TESTBED_HelperReply *reply;
293 struct GNUNET_CONFIGURATION_Handle *cfg;
294 struct WriteContext *wc;
295 char *binary;
296 char *trusted_ip;
297 char *hostname;
298 char *config;
299 char *xconfig;
300 char *evstr;
301 // char *str;
302 size_t config_size;
303 uLongf ul_config_size;
304 size_t xconfig_size;
305 uint16_t trusted_ip_size;
306 uint16_t hostname_size;
307 uint16_t msize;
308
309 msize = ntohs (message->size);
310 if ((sizeof(struct GNUNET_TESTBED_HelperInit) >= msize) ||
311 (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_INIT != ntohs (message->type)))
312 {
313 LOG (GNUNET_ERROR_TYPE_WARNING, "Received unexpected message -- exiting\n");
314 goto error;
315 }
316 msg = (const struct GNUNET_TESTBED_HelperInit *) message;
317 trusted_ip_size = ntohs (msg->trusted_ip_size);
318 trusted_ip = (char *) &msg[1];
319 if ('\0' != trusted_ip[trusted_ip_size])
320 {
321 LOG (GNUNET_ERROR_TYPE_WARNING, "Trusted IP cannot be empty -- exiting\n");
322 goto error;
323 }
324 hostname_size = ntohs (msg->hostname_size);
325 if ((sizeof(struct GNUNET_TESTBED_HelperInit) + trusted_ip_size + 1
326 + hostname_size) >= msize)
327 {
328 GNUNET_break (0);
329 LOG (GNUNET_ERROR_TYPE_WARNING, "Received unexpected message -- exiting\n");
330 goto error;
331 }
332 ul_config_size = (uLongf) ntohs (msg->config_size);
333 config = GNUNET_malloc (ul_config_size);
334 xconfig_size = msize - (trusted_ip_size + 1 + hostname_size
335 + sizeof(struct GNUNET_TESTBED_HelperInit));
336 int ret = uncompress ((Bytef *) config,
337 &ul_config_size,
338 (const Bytef *) (trusted_ip + trusted_ip_size + 1
339 + hostname_size),
340 (uLongf) xconfig_size);
341 if (Z_OK != ret)
342 {
343 switch (ret)
344 {
345 case Z_MEM_ERROR:
346 LOG (GNUNET_ERROR_TYPE_ERROR, "Not enough memory for decompression\n");
347 break;
348
349 case Z_BUF_ERROR:
350 LOG (GNUNET_ERROR_TYPE_ERROR, "Output buffer too small\n");
351 break;
352
353 case Z_DATA_ERROR:
354 LOG (GNUNET_ERROR_TYPE_ERROR, "Data corrupted/incomplete\n");
355 break;
356
357 default:
358 GNUNET_break (0);
359 }
360 LOG (GNUNET_ERROR_TYPE_ERROR,
361 "Error while uncompressing config -- exiting\n");
362 GNUNET_free (config);
363 goto error;
364 }
365 cfg = GNUNET_CONFIGURATION_create ();
366 if (GNUNET_OK !=
367 GNUNET_CONFIGURATION_deserialize (cfg, config, ul_config_size, NULL))
368 {
369 LOG (GNUNET_ERROR_TYPE_ERROR, "Unable to deserialize config -- exiting\n");
370 GNUNET_free (config);
371 goto error;
372 }
373 GNUNET_free (config);
374 hostname = NULL;
375 if (0 != hostname_size)
376 {
377 hostname = GNUNET_malloc (hostname_size + 1);
378 GNUNET_strlcpy (hostname,
379 ((char *) &msg[1]) + trusted_ip_size + 1,
380 hostname_size + 1);
381 }
382 /* unset GNUNET_TESTING_PREFIX if present as it is more relevant for testbed */
383 evstr = getenv (GNUNET_TESTING_PREFIX);
384 if (NULL != evstr)
385 {
386 /* unsetting the variable will invalidate the pointer! */
387 evstr = GNUNET_strdup (evstr);
388 GNUNET_break (0 == unsetenv (GNUNET_TESTING_PREFIX));
389 }
390 test_system =
391 GNUNET_TESTING_system_create ("testbed-helper", trusted_ip, hostname, NULL);
392 if (NULL != evstr)
393 {
394 char *evar;
395
396 GNUNET_asprintf (&evar, GNUNET_TESTING_PREFIX "=%s", evstr);
397 GNUNET_assert (0 == putenv (evar)); /* consumes 'evar',
398 see putenv(): becomes part of environment! */
399 GNUNET_free (evstr);
400 evstr = NULL;
401 }
402 GNUNET_free (hostname);
403 hostname = NULL;
404 GNUNET_assert (NULL != test_system);
405 GNUNET_assert (GNUNET_OK ==
406 GNUNET_TESTING_configuration_create (test_system, cfg));
407 GNUNET_assert (GNUNET_OK ==
408 GNUNET_CONFIGURATION_get_value_filename (cfg,
409 "PATHS",
410 "DEFAULTCONFIG",
411 &config));
412 if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg, config))
413 {
414 LOG (GNUNET_ERROR_TYPE_WARNING,
415 "Unable to write config file: %s -- exiting\n",
416 config);
417 GNUNET_CONFIGURATION_destroy (cfg);
418 GNUNET_free (config);
419 goto error;
420 }
421 LOG_DEBUG ("Staring testbed with config: %s\n", config);
422 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-service-testbed");
423 {
424 char *evar;
425
426 /* expose testbed configuration through env variable */
427 GNUNET_asprintf (&evar, "%s=%s", ENV_TESTBED_CONFIG, config);
428 GNUNET_assert (0 == putenv (evar)); /* consumes 'evar',
429 see putenv(): becomes part of environment! */
430 evstr = NULL;
431 }
432 testbed = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR /*verbose? */,
433 NULL,
434 NULL,
435 NULL,
436 binary,
437 "gnunet-service-testbed",
438 "-c",
439 config,
440 NULL);
441 GNUNET_free (binary);
442 GNUNET_free (config);
443 if (NULL == testbed)
444 {
445 LOG (GNUNET_ERROR_TYPE_WARNING,
446 "Error starting gnunet-service-testbed -- exiting\n");
447 GNUNET_CONFIGURATION_destroy (cfg);
448 goto error;
449 }
450 done_reading = GNUNET_YES;
451 config = GNUNET_CONFIGURATION_serialize (cfg, &config_size);
452 GNUNET_CONFIGURATION_destroy (cfg);
453 cfg = NULL;
454 xconfig_size =
455 GNUNET_TESTBED_compress_config_ (config, config_size, &xconfig);
456 GNUNET_free (config);
457 wc = GNUNET_new (struct WriteContext);
458 wc->length = xconfig_size + sizeof(struct GNUNET_TESTBED_HelperReply);
459 reply = GNUNET_realloc (xconfig, wc->length);
460 memmove (&reply[1], reply, xconfig_size);
461 reply->header.type = htons (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_REPLY);
462 reply->header.size = htons ((uint16_t) wc->length);
463 reply->config_size = htons ((uint16_t) config_size);
464 wc->data = reply;
465 write_task_id = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
466 stdout_fd,
467 &write_task,
468 wc);
469 child_death_task_id = GNUNET_SCHEDULER_add_read_file (
470 GNUNET_TIME_UNIT_FOREVER_REL,
471 GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ),
472 &child_death_task,
473 NULL);
474 return GNUNET_OK;
475
476error:
477 status = GNUNET_SYSERR;
478 GNUNET_SCHEDULER_shutdown ();
479 return GNUNET_SYSERR;
480}
481
482
483/**
484 * Task to read from stdin
485 *
486 * @param cls NULL
487 */
488static void
489read_task (void *cls)
490{
491 char buf[GNUNET_MAX_MESSAGE_SIZE];
492 ssize_t sread;
493
494 read_task_id = NULL;
495 sread = GNUNET_DISK_file_read (stdin_fd, buf, sizeof(buf));
496 if ((GNUNET_SYSERR == sread) || (0 == sread))
497 {
498 LOG_DEBUG ("STDIN closed\n");
499 GNUNET_SCHEDULER_shutdown ();
500 return;
501 }
502 if (GNUNET_YES == done_reading)
503 {
504 /* didn't expect any more data! */
505 GNUNET_break_op (0);
506 GNUNET_SCHEDULER_shutdown ();
507 return;
508 }
509 LOG_DEBUG ("Read %u bytes\n", (unsigned int) sread);
510 /* FIXME: could introduce a GNUNET_MST_read2 to read
511 directly from 'stdin_fd' and save a memcpy() here */
512 if (GNUNET_OK !=
513 GNUNET_MST_from_buffer (tokenizer, buf, sread, GNUNET_NO, GNUNET_NO))
514 {
515 GNUNET_break (0);
516 GNUNET_SCHEDULER_shutdown ();
517 return;
518 }
519 read_task_id /* No timeout while reading */
520 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
521 stdin_fd,
522 &read_task,
523 NULL);
524}
525
526
527/**
528 * Main function that will be run.
529 *
530 * @param cls closure
531 * @param args remaining command-line arguments
532 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
533 * @param cfg configuration
534 */
535static void
536run (void *cls,
537 char *const *args,
538 const char *cfgfile,
539 const struct GNUNET_CONFIGURATION_Handle *cfg)
540{
541 LOG_DEBUG ("Starting testbed helper...\n");
542 tokenizer = GNUNET_MST_create (&tokenizer_cb, NULL);
543 stdin_fd = GNUNET_DISK_get_handle_from_native (stdin);
544 stdout_fd = GNUNET_DISK_get_handle_from_native (stdout);
545 read_task_id = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
546 stdin_fd,
547 &read_task,
548 NULL);
549 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
550}
551
552
553/**
554 * Signal handler called for SIGCHLD.
555 */
556static void
557sighandler_child_death ()
558{
559 static char c;
560 int old_errno; /* back-up errno */
561
562 old_errno = errno;
563 GNUNET_break (
564 1 ==
565 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle (sigpipe,
566 GNUNET_DISK_PIPE_END_WRITE),
567 &c,
568 sizeof(c)));
569 errno = old_errno;
570}
571
572
573/**
574 * Main function
575 *
576 * @param argc the number of command line arguments
577 * @param argv command line arg array
578 * @return return code
579 */
580int
581main (int argc, char **argv)
582{
583 struct GNUNET_SIGNAL_Context *shc_chld;
584 struct GNUNET_GETOPT_CommandLineOption options[] =
585 { GNUNET_GETOPT_OPTION_END };
586 int ret;
587
588 status = GNUNET_OK;
589 if (NULL ==
590 (sigpipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE)))
591 {
592 GNUNET_break (0);
593 return 1;
594 }
595 shc_chld =
596 GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death);
597 ret = GNUNET_PROGRAM_run (argc,
598 argv,
599 "gnunet-helper-testbed",
600 "Helper for starting gnunet-service-testbed",
601 options,
602 &run,
603 NULL);
604 GNUNET_SIGNAL_handler_uninstall (shc_chld);
605 shc_chld = NULL;
606 GNUNET_DISK_pipe_close (sigpipe);
607 if (GNUNET_OK != ret)
608 return 1;
609 return (GNUNET_OK == status) ? 0 : 1;
610}
611
612
613/* end of gnunet-helper-testbed.c */