diff options
Diffstat (limited to 'src/testing/testing.c')
-rw-r--r-- | src/testing/testing.c | 1010 |
1 files changed, 1010 insertions, 0 deletions
diff --git a/src/testing/testing.c b/src/testing/testing.c new file mode 100644 index 000000000..c4da3a973 --- /dev/null +++ b/src/testing/testing.c | |||
@@ -0,0 +1,1010 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | (C) 2008, 2009, 2012 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 3, or (at your | ||
8 | 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 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file testing/testing_new.c | ||
23 | * @brief convenience API for writing testcases for GNUnet | ||
24 | * Many testcases need to start and stop a peer/service | ||
25 | * and this library is supposed to make that easier | ||
26 | * for TESTCASES. Normal programs should always | ||
27 | * use functions from gnunet_{util,arm}_lib.h. This API is | ||
28 | * ONLY for writing testcases (or internal use of the testbed). | ||
29 | * @author Christian Grothoff | ||
30 | * | ||
31 | */ | ||
32 | #include "platform.h" | ||
33 | #include "gnunet_util_lib.h" | ||
34 | #include "gnunet_testing_lib-new.h" | ||
35 | |||
36 | #define LOG(kind,...) \ | ||
37 | GNUNET_log_from (kind, "gnunettestingnew", __VA_ARGS__) | ||
38 | |||
39 | |||
40 | /** | ||
41 | * Size of a hostkey when written to a file | ||
42 | */ | ||
43 | #define HOSTKEYFILESIZE 914 | ||
44 | |||
45 | |||
46 | /** | ||
47 | * Handle for a system on which GNUnet peers are executed; | ||
48 | * a system is used for reserving unique paths and ports. | ||
49 | */ | ||
50 | struct GNUNET_TESTING_System | ||
51 | { | ||
52 | /** | ||
53 | * Prefix (i.e. "/tmp/gnunet-testing/") we prepend to each | ||
54 | * SERVICEHOME. | ||
55 | */ | ||
56 | char *tmppath; | ||
57 | |||
58 | /** | ||
59 | * The hostname of the controller | ||
60 | */ | ||
61 | char *controller; | ||
62 | |||
63 | /** | ||
64 | * Hostkeys data, contains "HOSTKEYFILESIZE * total_hostkeys" bytes. | ||
65 | */ | ||
66 | char *hostkeys_data; | ||
67 | |||
68 | /** | ||
69 | * Bitmap where each TCP port that has already been reserved for | ||
70 | * some GNUnet peer is recorded. Note that we additionally need to | ||
71 | * test if a port is already in use by non-GNUnet components before | ||
72 | * assigning it to a peer/service. If we detect that a port is | ||
73 | * already in use, we also mark it in this bitmap. So all the bits | ||
74 | * that are zero merely indicate ports that MIGHT be available for | ||
75 | * peers. | ||
76 | */ | ||
77 | uint32_t reserved_tcp_ports[65536 / 32]; | ||
78 | |||
79 | /** | ||
80 | * Bitmap where each UDP port that has already been reserved for | ||
81 | * some GNUnet peer is recorded. Note that we additionally need to | ||
82 | * test if a port is already in use by non-GNUnet components before | ||
83 | * assigning it to a peer/service. If we detect that a port is | ||
84 | * already in use, we also mark it in this bitmap. So all the bits | ||
85 | * that are zero merely indicate ports that MIGHT be available for | ||
86 | * peers. | ||
87 | */ | ||
88 | uint32_t reserved_udp_ports[65536 / 32]; | ||
89 | |||
90 | /** | ||
91 | * Counter we use to make service home paths unique on this system; | ||
92 | * the full path consists of the tmppath and this number. Each | ||
93 | * UNIXPATH for a peer is also modified to include the respective | ||
94 | * path counter to ensure uniqueness. This field is incremented | ||
95 | * by one for each configured peer. Even if peers are destroyed, | ||
96 | * we never re-use path counters. | ||
97 | */ | ||
98 | uint32_t path_counter; | ||
99 | |||
100 | /** | ||
101 | * The number of hostkeys | ||
102 | */ | ||
103 | uint32_t total_hostkeys; | ||
104 | }; | ||
105 | |||
106 | |||
107 | /** | ||
108 | * Handle for a GNUnet peer controlled by testing. | ||
109 | */ | ||
110 | struct GNUNET_TESTING_Peer | ||
111 | { | ||
112 | |||
113 | /** | ||
114 | * Path to the configuration file for this peer. | ||
115 | */ | ||
116 | char *cfgfile; | ||
117 | |||
118 | /** | ||
119 | * Binary to be executed during 'GNUNET_TESTING_peer_start'. | ||
120 | * Typically 'gnunet-service-arm' (but can be set to a | ||
121 | * specific service by 'GNUNET_TESTING_service_run' if | ||
122 | * necessary). | ||
123 | */ | ||
124 | char *main_binary; | ||
125 | |||
126 | /** | ||
127 | * Handle to the running binary of the service, NULL if the | ||
128 | * peer/service is currently not running. | ||
129 | */ | ||
130 | struct GNUNET_OS_Process *main_process; | ||
131 | }; | ||
132 | |||
133 | |||
134 | /** | ||
135 | * Lowest port used for GNUnet testing. Should be high enough to not | ||
136 | * conflict with other applications running on the hosts but be low | ||
137 | * enough to not conflict with client-ports (typically starting around | ||
138 | * 32k). | ||
139 | */ | ||
140 | #define LOW_PORT 12000 | ||
141 | |||
142 | |||
143 | /** | ||
144 | * Highest port used for GNUnet testing. Should be low enough to not | ||
145 | * conflict with the port range for "local" ports (client apps; see | ||
146 | * /proc/sys/net/ipv4/ip_local_port_range on Linux for example). | ||
147 | */ | ||
148 | #define HIGH_PORT 56000 | ||
149 | |||
150 | |||
151 | /** | ||
152 | * Create a system handle. There must only be one system | ||
153 | * handle per operating system. | ||
154 | * | ||
155 | * @param tmppath prefix path to use for all service homes | ||
156 | * @param controller hostname of the controlling host, | ||
157 | * service configurations are modified to allow | ||
158 | * control connections from this host; can be NULL | ||
159 | * @return handle to this system, NULL on error | ||
160 | */ | ||
161 | struct GNUNET_TESTING_System * | ||
162 | GNUNET_TESTING_system_create (const char *tmppath, | ||
163 | const char *controller) | ||
164 | { | ||
165 | struct GNUNET_TESTING_System *system; | ||
166 | |||
167 | if (NULL == tmppath) | ||
168 | { | ||
169 | LOG (GNUNET_ERROR_TYPE_ERROR, _("tmppath cannot be NULL\n")); | ||
170 | return NULL; | ||
171 | } | ||
172 | system = GNUNET_malloc (sizeof (struct GNUNET_TESTING_System)); | ||
173 | system->tmppath = GNUNET_strdup (tmppath); | ||
174 | if (NULL != controller) | ||
175 | system->controller = GNUNET_strdup (controller); | ||
176 | return system; | ||
177 | } | ||
178 | |||
179 | |||
180 | /** | ||
181 | * Free system resources. | ||
182 | * | ||
183 | * @param system system to be freed | ||
184 | * @param remove_paths should the 'tmppath' and all subdirectories | ||
185 | * be removed (clean up on shutdown)? | ||
186 | */ | ||
187 | void | ||
188 | GNUNET_TESTING_system_destroy (struct GNUNET_TESTING_System *system, | ||
189 | int remove_paths) | ||
190 | { | ||
191 | if (NULL != system->hostkeys_data) | ||
192 | { | ||
193 | GNUNET_break (0); /* Use GNUNET_TESTING_hostkeys_unload() */ | ||
194 | GNUNET_TESTING_hostkeys_unload (system); | ||
195 | } | ||
196 | if (GNUNET_YES == remove_paths) | ||
197 | GNUNET_DISK_directory_remove (system->tmppath); | ||
198 | GNUNET_free (system->tmppath); | ||
199 | GNUNET_free_non_null (system->controller); | ||
200 | GNUNET_free (system); | ||
201 | } | ||
202 | |||
203 | |||
204 | /** | ||
205 | * Reserve a TCP or UDP port for a peer. | ||
206 | * | ||
207 | * @param system system to use for reservation tracking | ||
208 | * @param is_tcp GNUNET_YES for TCP ports, GNUNET_NO for UDP | ||
209 | * @return 0 if no free port was available | ||
210 | */ | ||
211 | uint16_t | ||
212 | GNUNET_TESTING_reserve_port (struct GNUNET_TESTING_System *system, | ||
213 | int is_tcp) | ||
214 | { | ||
215 | struct GNUNET_NETWORK_Handle *socket; | ||
216 | struct addrinfo hint; | ||
217 | struct addrinfo *ret; | ||
218 | uint32_t *port_buckets; | ||
219 | char *open_port_str; | ||
220 | int bind_status; | ||
221 | uint32_t xor_image; | ||
222 | uint16_t index; | ||
223 | uint16_t open_port; | ||
224 | uint16_t pos; | ||
225 | |||
226 | /* | ||
227 | FIXME: Instead of using getaddrinfo we should try to determine the port | ||
228 | status by the following heurestics. | ||
229 | |||
230 | On systems which support both IPv4 and IPv6, only ports open on both | ||
231 | address families are considered open. | ||
232 | On system with either IPv4 or IPv6. A port is considered open if it's | ||
233 | open in the respective address family | ||
234 | */ | ||
235 | hint.ai_family = AF_UNSPEC; /* IPv4 and IPv6 */ | ||
236 | hint.ai_socktype = (GNUNET_YES == is_tcp)? SOCK_STREAM : SOCK_DGRAM; | ||
237 | hint.ai_protocol = 0; | ||
238 | hint.ai_addrlen = 0; | ||
239 | hint.ai_addr = NULL; | ||
240 | hint.ai_canonname = NULL; | ||
241 | hint.ai_next = NULL; | ||
242 | hint.ai_flags = AI_PASSIVE | AI_NUMERICSERV; /* Wild card address */ | ||
243 | port_buckets = (GNUNET_YES == is_tcp) ? | ||
244 | system->reserved_tcp_ports : system->reserved_udp_ports; | ||
245 | for (index = (LOW_PORT / 32) + 1; index < (HIGH_PORT / 32); index++) | ||
246 | { | ||
247 | xor_image = (UINT32_MAX ^ port_buckets[index]); | ||
248 | if (0 == xor_image) /* Ports in the bucket are full */ | ||
249 | continue; | ||
250 | pos = 0; | ||
251 | while (pos < 32) | ||
252 | { | ||
253 | if (0 == ((xor_image >> pos) & 1U)) | ||
254 | { | ||
255 | pos++; | ||
256 | continue; | ||
257 | } | ||
258 | open_port = (index * 32) + pos; | ||
259 | GNUNET_asprintf (&open_port_str, "%u", (unsigned int) open_port); | ||
260 | ret = NULL; | ||
261 | GNUNET_assert (0 == getaddrinfo (NULL, open_port_str, &hint, &ret)); | ||
262 | GNUNET_free (open_port_str); | ||
263 | socket = GNUNET_NETWORK_socket_create (ret->ai_family, | ||
264 | (GNUNET_YES == is_tcp) ? | ||
265 | SOCK_STREAM : SOCK_DGRAM, | ||
266 | 0); | ||
267 | GNUNET_assert (NULL != socket); | ||
268 | bind_status = GNUNET_NETWORK_socket_bind (socket, | ||
269 | ret->ai_addr, | ||
270 | ret->ai_addrlen); | ||
271 | freeaddrinfo (ret); | ||
272 | GNUNET_NETWORK_socket_close (socket); | ||
273 | socket = NULL; | ||
274 | port_buckets[index] |= (1U << pos); /* Set the port bit */ | ||
275 | if (GNUNET_OK == bind_status) | ||
276 | { | ||
277 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
278 | "Found a free port %u\n", (unsigned int) open_port); | ||
279 | return open_port; | ||
280 | } | ||
281 | pos++; | ||
282 | } | ||
283 | } | ||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | |||
288 | /** | ||
289 | * Release reservation of a TCP or UDP port for a peer | ||
290 | * (used during GNUNET_TESTING_peer_destroy). | ||
291 | * | ||
292 | * @param system system to use for reservation tracking | ||
293 | * @param is_tcp GNUNET_YES for TCP ports, GNUNET_NO for UDP | ||
294 | * @param port reserved port to release | ||
295 | */ | ||
296 | void | ||
297 | GNUNET_TESTING_release_port (struct GNUNET_TESTING_System *system, | ||
298 | int is_tcp, | ||
299 | uint16_t port) | ||
300 | { | ||
301 | uint32_t *port_buckets; | ||
302 | uint16_t bucket; | ||
303 | uint16_t pos; | ||
304 | |||
305 | port_buckets = (GNUNET_YES == is_tcp) ? | ||
306 | system->reserved_tcp_ports : system->reserved_udp_ports; | ||
307 | bucket = port / 32; | ||
308 | pos = port % 32; | ||
309 | LOG (GNUNET_ERROR_TYPE_DEBUG, "Releasing port %u\n", port); | ||
310 | if (0 == (port_buckets[bucket] & (1U << pos))) | ||
311 | { | ||
312 | GNUNET_break(0); /* Port was not reserved by us using reserve_port() */ | ||
313 | return; | ||
314 | } | ||
315 | port_buckets[bucket] &= ~(1U << pos); | ||
316 | } | ||
317 | |||
318 | |||
319 | /** | ||
320 | * Reserve a SERVICEHOME path for a peer. | ||
321 | * | ||
322 | * @param system system to use for reservation tracking | ||
323 | * @return NULL on error, otherwise fresh unique path to use | ||
324 | * as the servicehome for the peer; must be freed by the caller | ||
325 | */ | ||
326 | // static | ||
327 | char * | ||
328 | reserve_path (struct GNUNET_TESTING_System *system) | ||
329 | { | ||
330 | char *reserved_path; | ||
331 | |||
332 | GNUNET_asprintf (&reserved_path, | ||
333 | "%s/%u", system->tmppath, system->path_counter++); | ||
334 | return reserved_path; | ||
335 | } | ||
336 | |||
337 | |||
338 | /** | ||
339 | * Testing includes a number of pre-created hostkeys for faster peer | ||
340 | * startup. This function loads such keys into memory from a file. | ||
341 | * | ||
342 | * @param system the testing system handle | ||
343 | * @param filename the path of the hostkeys file | ||
344 | * @return GNUNET_OK on success; GNUNET_SYSERR on error | ||
345 | */ | ||
346 | int | ||
347 | GNUNET_TESTING_hostkeys_load (struct GNUNET_TESTING_System *system, | ||
348 | const char *filename) | ||
349 | { | ||
350 | struct GNUNET_DISK_FileHandle *fd; | ||
351 | uint64_t fs; | ||
352 | |||
353 | if (GNUNET_YES != GNUNET_DISK_file_test (filename)) | ||
354 | { | ||
355 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
356 | _("Hostkeys file not found: %s\n"), filename); | ||
357 | return GNUNET_SYSERR; | ||
358 | } | ||
359 | /* Check hostkey file size, read entire thing into memory */ | ||
360 | fd = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READ, | ||
361 | GNUNET_DISK_PERM_NONE); | ||
362 | if (NULL == fd) | ||
363 | { | ||
364 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
365 | _("Could not open hostkeys file: %s\n"), filename); | ||
366 | return GNUNET_SYSERR; | ||
367 | } | ||
368 | if (GNUNET_OK != | ||
369 | GNUNET_DISK_file_size (filename, &fs, GNUNET_YES, GNUNET_YES)) | ||
370 | fs = 0; | ||
371 | if (0 == fs) | ||
372 | { | ||
373 | GNUNET_DISK_file_close (fd); | ||
374 | return GNUNET_SYSERR; /* File is empty */ | ||
375 | } | ||
376 | if (0 != (fs % HOSTKEYFILESIZE)) | ||
377 | { | ||
378 | GNUNET_DISK_file_close (fd); | ||
379 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
380 | _("Incorrect hostkey file format: %s\n"), filename); | ||
381 | return GNUNET_SYSERR; | ||
382 | } | ||
383 | GNUNET_break (NULL == system->hostkeys_data); | ||
384 | system->total_hostkeys = fs / HOSTKEYFILESIZE; | ||
385 | system->hostkeys_data = GNUNET_malloc_large (fs); /* free in hostkeys_unload */ | ||
386 | GNUNET_assert (fs == GNUNET_DISK_file_read (fd, system->hostkeys_data, fs)); | ||
387 | GNUNET_DISK_file_close (fd); | ||
388 | return GNUNET_OK; | ||
389 | } | ||
390 | |||
391 | |||
392 | /** | ||
393 | * Function to remove the loaded hostkeys | ||
394 | * | ||
395 | * @param system the testing system handle | ||
396 | */ | ||
397 | void | ||
398 | GNUNET_TESTING_hostkeys_unload (struct GNUNET_TESTING_System *system) | ||
399 | { | ||
400 | GNUNET_break (NULL != system->hostkeys_data); | ||
401 | GNUNET_free_non_null (system->hostkeys_data); | ||
402 | system->hostkeys_data = NULL; | ||
403 | system->total_hostkeys = 0; | ||
404 | } | ||
405 | |||
406 | |||
407 | /** | ||
408 | * Testing includes a number of pre-created hostkeys for | ||
409 | * faster peer startup. This function can be used to | ||
410 | * access the n-th key of those pre-created hostkeys; note | ||
411 | * that these keys are ONLY useful for testing and not | ||
412 | * secure as the private keys are part of the public | ||
413 | * GNUnet source code. | ||
414 | * | ||
415 | * This is primarily a helper function used internally | ||
416 | * by 'GNUNET_TESTING_peer_configure'. | ||
417 | * | ||
418 | * @param system the testing system handle | ||
419 | * @param key_number desired pre-created hostkey to obtain | ||
420 | * @param id set to the peer's identity (hash of the public | ||
421 | * key; if NULL, GNUNET_SYSERR is returned immediately | ||
422 | * @return GNUNET_SYSERR on error (not enough keys) | ||
423 | */ | ||
424 | int | ||
425 | GNUNET_TESTING_hostkey_get (const struct GNUNET_TESTING_System *system, | ||
426 | uint32_t key_number, | ||
427 | struct GNUNET_PeerIdentity *id) | ||
428 | { | ||
429 | struct GNUNET_CRYPTO_RsaPrivateKey *private_key; | ||
430 | struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key; | ||
431 | |||
432 | if ((NULL == id) || (NULL == system->hostkeys_data)) | ||
433 | return GNUNET_SYSERR; | ||
434 | if (key_number >= system->total_hostkeys) | ||
435 | { | ||
436 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
437 | _("Key number %u does not exist\n"), key_number); | ||
438 | return GNUNET_SYSERR; | ||
439 | } | ||
440 | private_key = GNUNET_CRYPTO_rsa_decode_key (system->hostkeys_data + | ||
441 | (key_number * HOSTKEYFILESIZE), | ||
442 | HOSTKEYFILESIZE); | ||
443 | if (NULL == private_key) | ||
444 | { | ||
445 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
446 | _("Error while decoding key %u\n"), key_number); | ||
447 | return GNUNET_SYSERR; | ||
448 | } | ||
449 | GNUNET_CRYPTO_rsa_key_get_public (private_key, &public_key); | ||
450 | GNUNET_CRYPTO_hash (&public_key, | ||
451 | sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), | ||
452 | &(id->hashPubKey)); | ||
453 | GNUNET_CRYPTO_rsa_key_free (private_key); | ||
454 | return GNUNET_OK; | ||
455 | } | ||
456 | |||
457 | |||
458 | /** | ||
459 | * Structure for holding data to build new configurations from a configuration | ||
460 | * template | ||
461 | */ | ||
462 | struct UpdateContext | ||
463 | { | ||
464 | /** | ||
465 | * The system for which we are building configurations | ||
466 | */ | ||
467 | struct GNUNET_TESTING_System *system; | ||
468 | |||
469 | /** | ||
470 | * The configuration we are building | ||
471 | */ | ||
472 | struct GNUNET_CONFIGURATION_Handle *cfg; | ||
473 | |||
474 | /** | ||
475 | * The customized service home path for this peer | ||
476 | */ | ||
477 | char *service_home; | ||
478 | |||
479 | /** | ||
480 | * build status - to signal error while building a configuration | ||
481 | */ | ||
482 | int status; | ||
483 | }; | ||
484 | |||
485 | |||
486 | /** | ||
487 | * Function to iterate over options. Copies | ||
488 | * the options to the target configuration, | ||
489 | * updating PORT values as needed. | ||
490 | * | ||
491 | * @param cls the UpdateContext | ||
492 | * @param section name of the section | ||
493 | * @param option name of the option | ||
494 | * @param value value of the option | ||
495 | */ | ||
496 | static void | ||
497 | update_config (void *cls, const char *section, const char *option, | ||
498 | const char *value) | ||
499 | { | ||
500 | struct UpdateContext *uc = cls; | ||
501 | unsigned int ival; | ||
502 | char cval[12]; | ||
503 | char uval[128]; | ||
504 | char *single_variable; | ||
505 | char *per_host_variable; | ||
506 | unsigned long long num_per_host; | ||
507 | uint16_t new_port; | ||
508 | |||
509 | if (GNUNET_OK != uc->status) | ||
510 | return; | ||
511 | if (! ((0 == strcmp (option, "PORT")) | ||
512 | || (0 == strcmp (option, "UNIXPATH")) | ||
513 | || (0 == strcmp (option, "HOSTNAME")))) | ||
514 | return; | ||
515 | GNUNET_asprintf (&single_variable, "single_%s_per_host", section); | ||
516 | GNUNET_asprintf (&per_host_variable, "num_%s_per_host", section); | ||
517 | if ((0 == strcmp (option, "PORT")) && (1 == SSCANF (value, "%u", &ival))) | ||
518 | { | ||
519 | if ((ival != 0) && | ||
520 | (GNUNET_YES != | ||
521 | GNUNET_CONFIGURATION_get_value_yesno (uc->cfg, "testing", | ||
522 | single_variable))) | ||
523 | { | ||
524 | /* FIXME: What about UDP? */ | ||
525 | new_port = GNUNET_TESTING_reserve_port (uc->system, GNUNET_YES); | ||
526 | if (0 == new_port) | ||
527 | { | ||
528 | uc->status = GNUNET_SYSERR; | ||
529 | return; | ||
530 | } | ||
531 | GNUNET_snprintf (cval, sizeof (cval), "%u", new_port); | ||
532 | value = cval; | ||
533 | } | ||
534 | else if ((ival != 0) && | ||
535 | (GNUNET_YES == | ||
536 | GNUNET_CONFIGURATION_get_value_yesno (uc->cfg, "testing", | ||
537 | single_variable)) && | ||
538 | GNUNET_CONFIGURATION_get_value_number (uc->cfg, "testing", | ||
539 | per_host_variable, | ||
540 | &num_per_host)) | ||
541 | { | ||
542 | /* GNUNET_snprintf (cval, sizeof (cval), "%u", */ | ||
543 | /* ival + ctx->fdnum % num_per_host); */ | ||
544 | /* value = cval; */ | ||
545 | GNUNET_break (0); /* FIXME */ | ||
546 | } | ||
547 | } | ||
548 | if (0 == strcmp (option, "UNIXPATH")) | ||
549 | { | ||
550 | if (GNUNET_YES != | ||
551 | GNUNET_CONFIGURATION_get_value_yesno (uc->cfg, "testing", | ||
552 | single_variable)) | ||
553 | { | ||
554 | GNUNET_snprintf (uval, sizeof (uval), "%s/%s.sock", | ||
555 | uc->service_home, section); | ||
556 | value = uval; | ||
557 | } | ||
558 | else if ((GNUNET_YES == | ||
559 | GNUNET_CONFIGURATION_get_value_number (uc->cfg, "testing", | ||
560 | per_host_variable, | ||
561 | &num_per_host)) && | ||
562 | (num_per_host > 0)) | ||
563 | { | ||
564 | GNUNET_break(0); /* FIXME */ | ||
565 | } | ||
566 | } | ||
567 | if ((0 == strcmp (option, "HOSTNAME")) && (NULL != uc->system->controller)) | ||
568 | { | ||
569 | value = uc->system->controller; | ||
570 | } | ||
571 | GNUNET_free (single_variable); | ||
572 | GNUNET_free (per_host_variable); | ||
573 | GNUNET_CONFIGURATION_set_value_string (uc->cfg, section, option, value); | ||
574 | } | ||
575 | |||
576 | |||
577 | /** | ||
578 | * Section iterator to set ACCEPT_FROM in all sections | ||
579 | * | ||
580 | * @param cls the UpdateContext | ||
581 | * @param section name of the section | ||
582 | */ | ||
583 | static void | ||
584 | update_config_sections (void *cls, | ||
585 | const char *section) | ||
586 | { | ||
587 | struct UpdateContext *uc = cls; | ||
588 | char *orig_allowed_hosts; | ||
589 | char *allowed_hosts; | ||
590 | |||
591 | if (GNUNET_OK != | ||
592 | GNUNET_CONFIGURATION_get_value_string (uc->cfg, section, "ACCEPT_FROM", | ||
593 | &orig_allowed_hosts)) | ||
594 | { | ||
595 | orig_allowed_hosts = GNUNET_strdup ("127.0.0.1;"); | ||
596 | } | ||
597 | if (NULL == uc->system->controller) | ||
598 | allowed_hosts = GNUNET_strdup (orig_allowed_hosts); | ||
599 | else | ||
600 | GNUNET_asprintf (&allowed_hosts, "%s%s;", orig_allowed_hosts, | ||
601 | uc->system->controller); | ||
602 | GNUNET_free (orig_allowed_hosts); | ||
603 | GNUNET_CONFIGURATION_set_value_string (uc->cfg, section, "ACCEPT_FROM", | ||
604 | allowed_hosts); | ||
605 | GNUNET_free (allowed_hosts); | ||
606 | } | ||
607 | |||
608 | |||
609 | /** | ||
610 | * Create a new configuration using the given configuration | ||
611 | * as a template; ports and paths will be modified to select | ||
612 | * available ports on the local system. If we run | ||
613 | * out of "*port" numbers, return SYSERR. | ||
614 | * | ||
615 | * This is primarily a helper function used internally | ||
616 | * by 'GNUNET_TESTING_peer_configure'. | ||
617 | * | ||
618 | * @param system system to use to coordinate resource usage | ||
619 | * @param cfg template configuration to update | ||
620 | * @return GNUNET_OK on success, GNUNET_SYSERR on error - the configuration will | ||
621 | * be incomplete and should not be used there upon | ||
622 | */ | ||
623 | int | ||
624 | GNUNET_TESTING_configuration_create (struct GNUNET_TESTING_System *system, | ||
625 | struct GNUNET_CONFIGURATION_Handle *cfg) | ||
626 | { | ||
627 | struct UpdateContext uc; | ||
628 | char *default_config; | ||
629 | |||
630 | uc.system = system; | ||
631 | uc.cfg = cfg; | ||
632 | uc.status = GNUNET_OK; | ||
633 | GNUNET_asprintf (&uc.service_home, "%s/%u", system->tmppath, | ||
634 | system->path_counter++); | ||
635 | GNUNET_asprintf (&default_config, "%s/config", uc.service_home); | ||
636 | GNUNET_CONFIGURATION_set_value_string (cfg, "PATHS", "DEFAULTCONFIG", | ||
637 | default_config); | ||
638 | GNUNET_free (default_config); | ||
639 | GNUNET_CONFIGURATION_set_value_string (cfg, "PATHS", "SERVICEHOME", | ||
640 | uc.service_home); | ||
641 | /* make PORTs and UNIXPATHs unique */ | ||
642 | GNUNET_CONFIGURATION_iterate (cfg, &update_config, &uc); | ||
643 | /* allow connections to services from system controller host */ | ||
644 | GNUNET_CONFIGURATION_iterate_sections (cfg, &update_config_sections, &uc); | ||
645 | /* enable loopback-based connections between peers */ | ||
646 | GNUNET_CONFIGURATION_set_value_string (cfg, | ||
647 | "nat", | ||
648 | "USE_LOCALADDR", "YES"); | ||
649 | GNUNET_free (uc.service_home); | ||
650 | return uc.status; | ||
651 | } | ||
652 | |||
653 | |||
654 | /** | ||
655 | * Configure a GNUnet peer. GNUnet must be installed on the local | ||
656 | * system and available in the PATH. | ||
657 | * | ||
658 | * @param system system to use to coordinate resource usage | ||
659 | * @param cfg configuration to use; will be UPDATED (to reflect needed | ||
660 | * changes in port numbers and paths) | ||
661 | * @param key_number number of the hostkey to use for the peer | ||
662 | * @param id identifier for the daemon, will be set, can be NULL | ||
663 | * @param emsg set to error message (set to NULL on success), can be NULL | ||
664 | * @return handle to the peer, NULL on error | ||
665 | */ | ||
666 | struct GNUNET_TESTING_Peer * | ||
667 | GNUNET_TESTING_peer_configure (struct GNUNET_TESTING_System *system, | ||
668 | struct GNUNET_CONFIGURATION_Handle *cfg, | ||
669 | uint32_t key_number, | ||
670 | struct GNUNET_PeerIdentity *id, | ||
671 | char **emsg) | ||
672 | { | ||
673 | struct GNUNET_TESTING_Peer *peer; | ||
674 | struct GNUNET_DISK_FileHandle *fd; | ||
675 | char *service_home; | ||
676 | char hostkey_filename[128]; | ||
677 | char *config_filename; | ||
678 | char *emsg_; | ||
679 | |||
680 | if (NULL != emsg) | ||
681 | *emsg = NULL; | ||
682 | if (GNUNET_OK != GNUNET_TESTING_configuration_create (system, cfg)) | ||
683 | { | ||
684 | GNUNET_asprintf (&emsg_, | ||
685 | _("Failed to create configuration for peer (not enough free ports?)\n")); | ||
686 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s", *emsg_); | ||
687 | if (NULL != emsg) | ||
688 | *emsg = emsg_; | ||
689 | else | ||
690 | GNUNET_free (emsg_); | ||
691 | return NULL; | ||
692 | } | ||
693 | if (key_number >= system->total_hostkeys) | ||
694 | { | ||
695 | GNUNET_asprintf (&emsg_, | ||
696 | _("You attempted to create a testbed with more than %u hosts. Please precompute more hostkeys first.\n"), | ||
697 | (unsigned int) system->total_hostkeys); | ||
698 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s", *emsg_); | ||
699 | if (NULL != emsg) | ||
700 | *emsg = emsg_; | ||
701 | else | ||
702 | GNUNET_free (emsg_); | ||
703 | return NULL; | ||
704 | } | ||
705 | if ((NULL != id) && | ||
706 | (GNUNET_SYSERR == GNUNET_TESTING_hostkey_get (system, key_number, id))) | ||
707 | { | ||
708 | GNUNET_asprintf (&emsg_, | ||
709 | _("Failed to initialize hostkey for peer %u\n"), | ||
710 | (unsigned int) key_number); | ||
711 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s", *emsg_); | ||
712 | if (NULL != emsg) | ||
713 | *emsg = emsg_; | ||
714 | else | ||
715 | GNUNET_free (emsg_); | ||
716 | return NULL; | ||
717 | } | ||
718 | GNUNET_assert (GNUNET_OK == | ||
719 | GNUNET_CONFIGURATION_get_value_string (cfg, "PATHS", | ||
720 | "SERVICEHOME", | ||
721 | &service_home)); | ||
722 | GNUNET_snprintf (hostkey_filename, sizeof (hostkey_filename), "%s/.hostkey", | ||
723 | service_home); | ||
724 | GNUNET_free (service_home); | ||
725 | fd = GNUNET_DISK_file_open (hostkey_filename, | ||
726 | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_WRITE, | ||
727 | GNUNET_DISK_PERM_USER_READ | ||
728 | | GNUNET_DISK_PERM_USER_WRITE); | ||
729 | if (NULL == fd) | ||
730 | { | ||
731 | GNUNET_break (0); | ||
732 | return NULL; | ||
733 | } | ||
734 | if (HOSTKEYFILESIZE != | ||
735 | GNUNET_DISK_file_write (fd, system->hostkeys_data | ||
736 | + (key_number * HOSTKEYFILESIZE), | ||
737 | HOSTKEYFILESIZE)) | ||
738 | { | ||
739 | GNUNET_asprintf (&emsg_, | ||
740 | _("Failed to write hostkey file for peer %u: %s\n"), | ||
741 | (unsigned int) key_number, | ||
742 | STRERROR (errno)); | ||
743 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s", *emsg_); | ||
744 | if (NULL != emsg) | ||
745 | *emsg = emsg_; | ||
746 | else | ||
747 | GNUNET_free (emsg_); | ||
748 | GNUNET_DISK_file_close (fd); | ||
749 | return NULL; | ||
750 | } | ||
751 | GNUNET_DISK_file_close (fd); | ||
752 | GNUNET_assert (GNUNET_OK == | ||
753 | GNUNET_CONFIGURATION_get_value_string | ||
754 | (cfg, "PATHS", "DEFAULTCONFIG", &config_filename)); | ||
755 | if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg, config_filename)) | ||
756 | { | ||
757 | GNUNET_asprintf (&emsg_, | ||
758 | _("Failed to write configuration file `%s' for peer %u: %s\n"), | ||
759 | config_filename, | ||
760 | (unsigned int) key_number, | ||
761 | STRERROR (errno)); | ||
762 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s", *emsg_); | ||
763 | if (NULL != emsg) | ||
764 | *emsg = emsg_; | ||
765 | else | ||
766 | GNUNET_free (emsg_); | ||
767 | GNUNET_free (config_filename); | ||
768 | return NULL; | ||
769 | } | ||
770 | peer = GNUNET_malloc (sizeof (struct GNUNET_TESTING_Peer)); | ||
771 | peer->cfgfile = config_filename; /* Free in peer_destroy */ | ||
772 | peer->main_binary = GNUNET_strdup ("gnunet-service-arm"); | ||
773 | return peer; | ||
774 | } | ||
775 | |||
776 | |||
777 | /** | ||
778 | * Start the peer. | ||
779 | * | ||
780 | * @param peer peer to start | ||
781 | * @return GNUNET_OK on success, GNUNET_SYSERR on error (i.e. peer already running) | ||
782 | */ | ||
783 | int | ||
784 | GNUNET_TESTING_peer_start (struct GNUNET_TESTING_Peer *peer) | ||
785 | { | ||
786 | if (NULL != peer->main_process) | ||
787 | { | ||
788 | GNUNET_break (0); | ||
789 | return GNUNET_SYSERR; | ||
790 | } | ||
791 | GNUNET_assert (NULL != peer->cfgfile); | ||
792 | peer->main_process = GNUNET_OS_start_process (GNUNET_YES, NULL, NULL, | ||
793 | peer->main_binary, | ||
794 | peer->main_binary, | ||
795 | "-c", | ||
796 | peer->cfgfile, | ||
797 | NULL); | ||
798 | if (NULL == peer->main_process) | ||
799 | { | ||
800 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
801 | _("Failed to start `%s': %s\n"), | ||
802 | peer->main_binary, | ||
803 | STRERROR (errno)); | ||
804 | return GNUNET_SYSERR; | ||
805 | } | ||
806 | return GNUNET_OK; | ||
807 | } | ||
808 | |||
809 | |||
810 | /** | ||
811 | * Stop the peer. | ||
812 | * | ||
813 | * @param peer peer to stop | ||
814 | * @return GNUNET_OK on success, GNUNET_SYSERR on error (i.e. peer not running) | ||
815 | */ | ||
816 | int | ||
817 | GNUNET_TESTING_peer_stop (struct GNUNET_TESTING_Peer *peer) | ||
818 | { | ||
819 | if (NULL == peer->main_process) | ||
820 | { | ||
821 | GNUNET_break (0); | ||
822 | return GNUNET_SYSERR; | ||
823 | } | ||
824 | (void) GNUNET_OS_process_kill (peer->main_process, SIGTERM); | ||
825 | GNUNET_assert (GNUNET_OK == GNUNET_OS_process_wait (peer->main_process)); | ||
826 | GNUNET_OS_process_destroy (peer->main_process); | ||
827 | peer->main_process = NULL; | ||
828 | return GNUNET_OK; | ||
829 | } | ||
830 | |||
831 | |||
832 | /** | ||
833 | * Destroy the peer. Releases resources locked during peer configuration. | ||
834 | * If the peer is still running, it will be stopped AND a warning will be | ||
835 | * printed (users of the API should stop the peer explicitly first). | ||
836 | * | ||
837 | * @param peer peer to destroy | ||
838 | */ | ||
839 | void | ||
840 | GNUNET_TESTING_peer_destroy (struct GNUNET_TESTING_Peer *peer) | ||
841 | { | ||
842 | if (NULL != peer->main_process) | ||
843 | { | ||
844 | GNUNET_break (0); | ||
845 | GNUNET_TESTING_peer_stop (peer); | ||
846 | } | ||
847 | GNUNET_free (peer->cfgfile); | ||
848 | GNUNET_free (peer->main_binary); | ||
849 | GNUNET_free (peer); | ||
850 | } | ||
851 | |||
852 | |||
853 | /** | ||
854 | * Start a single peer and run a test using the testing library. | ||
855 | * Starts a peer using the given configuration and then invokes the | ||
856 | * given callback. This function ALSO initializes the scheduler loop | ||
857 | * and should thus be called directly from "main". The testcase | ||
858 | * should self-terminate by invoking 'GNUNET_SCHEDULER_shutdown'. | ||
859 | * | ||
860 | * @param tmppath path for storing temporary data for the test | ||
861 | * @param cfgfilename name of the configuration file to use; | ||
862 | * use NULL to only run with defaults | ||
863 | * @param tm main function of the testcase | ||
864 | * @param tm_cls closure for 'tm' | ||
865 | * @return 0 on success, 1 on error | ||
866 | */ | ||
867 | int | ||
868 | GNUNET_TESTING_peer_run (const char *tmppath, | ||
869 | const char *cfgfilename, | ||
870 | GNUNET_TESTING_TestMain tm, | ||
871 | void *tm_cls) | ||
872 | { | ||
873 | return GNUNET_TESTING_service_run (tmppath, "arm", | ||
874 | cfgfilename, tm, tm_cls); | ||
875 | } | ||
876 | |||
877 | |||
878 | /** | ||
879 | * Structure for holding service data | ||
880 | */ | ||
881 | struct ServiceContext | ||
882 | { | ||
883 | /** | ||
884 | * The configuration of the peer in which the service is run | ||
885 | */ | ||
886 | const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
887 | |||
888 | /** | ||
889 | * Callback to signal service startup | ||
890 | */ | ||
891 | GNUNET_TESTING_TestMain tm; | ||
892 | |||
893 | /** | ||
894 | * Closure for the above callback | ||
895 | */ | ||
896 | void *tm_cls; | ||
897 | }; | ||
898 | |||
899 | |||
900 | /** | ||
901 | * Callback to be called when SCHEDULER has been started | ||
902 | * | ||
903 | * @param cls the ServiceContext | ||
904 | * @param tc the TaskContext | ||
905 | */ | ||
906 | static void | ||
907 | service_run_main (void *cls, | ||
908 | const struct GNUNET_SCHEDULER_TaskContext *tc) | ||
909 | { | ||
910 | struct ServiceContext *sc = cls; | ||
911 | |||
912 | sc->tm (sc->tm_cls, sc->cfg); | ||
913 | } | ||
914 | |||
915 | |||
916 | /** | ||
917 | * Start a single service (no ARM, except of course if the given | ||
918 | * service name is 'arm') and run a test using the testing library. | ||
919 | * Starts a service using the given configuration and then invokes the | ||
920 | * given callback. This function ALSO initializes the scheduler loop | ||
921 | * and should thus be called directly from "main". The testcase | ||
922 | * should self-terminate by invoking 'GNUNET_SCHEDULER_shutdown'. | ||
923 | * | ||
924 | * This function is useful if the testcase is for a single service | ||
925 | * and if that service doesn't itself depend on other services. | ||
926 | * | ||
927 | * @param tmppath path for storing temporary data for the test | ||
928 | * @param service_name name of the service to run | ||
929 | * @param cfgfilename name of the configuration file to use; | ||
930 | * use NULL to only run with defaults | ||
931 | * @param tm main function of the testcase | ||
932 | * @param tm_cls closure for 'tm' | ||
933 | * @return 0 on success, 1 on error | ||
934 | */ | ||
935 | int | ||
936 | GNUNET_TESTING_service_run (const char *tmppath, | ||
937 | const char *service_name, | ||
938 | const char *cfgfilename, | ||
939 | GNUNET_TESTING_TestMain tm, | ||
940 | void *tm_cls) | ||
941 | { | ||
942 | struct ServiceContext sc; | ||
943 | struct GNUNET_TESTING_System *system; | ||
944 | struct GNUNET_TESTING_Peer *peer; | ||
945 | struct GNUNET_CONFIGURATION_Handle *cfg; | ||
946 | char *data_dir; | ||
947 | char *hostkeys_file; | ||
948 | |||
949 | data_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR); | ||
950 | GNUNET_asprintf (&hostkeys_file, "%s/testing_hostkeys.dat", data_dir); | ||
951 | GNUNET_free (data_dir); | ||
952 | system = GNUNET_TESTING_system_create (tmppath, "127.0.0.1"); | ||
953 | if (NULL == system) | ||
954 | { | ||
955 | GNUNET_free (hostkeys_file); | ||
956 | return 1; | ||
957 | } | ||
958 | if (GNUNET_OK != GNUNET_TESTING_hostkeys_load (system, hostkeys_file)) | ||
959 | { | ||
960 | GNUNET_free (hostkeys_file); | ||
961 | GNUNET_TESTING_system_destroy (system, GNUNET_YES); | ||
962 | return 1; | ||
963 | } | ||
964 | GNUNET_free (hostkeys_file); | ||
965 | cfg = GNUNET_CONFIGURATION_create (); | ||
966 | if (GNUNET_OK != GNUNET_CONFIGURATION_load (cfg, cfgfilename)) | ||
967 | { | ||
968 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
969 | _("Failed to load configuration from %s\n"), cfgfilename); | ||
970 | GNUNET_CONFIGURATION_destroy (cfg); | ||
971 | GNUNET_TESTING_system_destroy (system, GNUNET_YES); | ||
972 | return 1; | ||
973 | } | ||
974 | peer = GNUNET_TESTING_peer_configure (system, cfg, 0, NULL, NULL); | ||
975 | if (NULL == peer) | ||
976 | { | ||
977 | GNUNET_CONFIGURATION_destroy (cfg); | ||
978 | GNUNET_TESTING_hostkeys_unload (system); | ||
979 | GNUNET_TESTING_system_destroy (system, GNUNET_YES); | ||
980 | return 1; | ||
981 | } | ||
982 | GNUNET_TESTING_hostkeys_unload (system); | ||
983 | GNUNET_free (peer->main_binary); | ||
984 | GNUNET_asprintf (&peer->main_binary, "gnunet-service-%s", service_name); | ||
985 | if (GNUNET_OK != GNUNET_TESTING_peer_start (peer)) | ||
986 | { | ||
987 | GNUNET_TESTING_peer_destroy (peer); | ||
988 | GNUNET_CONFIGURATION_destroy (cfg); | ||
989 | GNUNET_TESTING_system_destroy (system, GNUNET_YES); | ||
990 | return 1; | ||
991 | } | ||
992 | sc.cfg = cfg; | ||
993 | sc.tm = tm; | ||
994 | sc.tm_cls = tm_cls; | ||
995 | GNUNET_SCHEDULER_run (&service_run_main, &sc); /* Scheduler loop */ | ||
996 | if (GNUNET_OK != GNUNET_TESTING_peer_stop (peer)) | ||
997 | { | ||
998 | GNUNET_TESTING_peer_destroy (peer); | ||
999 | GNUNET_CONFIGURATION_destroy (cfg); | ||
1000 | GNUNET_TESTING_system_destroy (system, GNUNET_YES); | ||
1001 | return 1; | ||
1002 | } | ||
1003 | GNUNET_TESTING_peer_destroy (peer); | ||
1004 | GNUNET_CONFIGURATION_destroy (cfg); | ||
1005 | GNUNET_TESTING_system_destroy (system, GNUNET_YES); | ||
1006 | return 0; | ||
1007 | } | ||
1008 | |||
1009 | |||
1010 | /* end of testing_new.c */ | ||