2 This file is part of GNUnet
3 (C) 2008, 2009 Christian Grothoff (and other contributing authors)
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 2, or (at your
8 option) any later version.
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.
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.
22 * @file testing/testing_group.c
23 * @brief convenience API for writing testcases for GNUnet
24 * @author Christian Grothoff
27 #include "gnunet_arm_service.h"
28 #include "gnunet_testing_lib.h"
30 #define VERBOSE_TESTING GNUNET_YES
33 * Lowest port used for GNUnet testing. Should be high enough to not
34 * conflict with other applications running on the hosts but be low
35 * enough to not conflict with client-ports (typically starting around
38 #define LOW_PORT 10000
41 * Highest port used for GNUnet testing. Should be low enough to not
42 * conflict with the port range for "local" ports (client apps; see
43 * /proc/sys/net/ipv4/ip_local_port_range on Linux for example).
45 #define HIGH_PORT 32000
47 #define CONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
54 struct PeerConnection *next;
57 * Pointer to daemon handle
59 struct GNUNET_TESTING_Daemon *daemon;
64 * Data we keep per peer.
69 * (Initial) configuration of the host.
70 * (initial because clients could change
71 * it and we would not know about those
74 struct GNUNET_CONFIGURATION_Handle *cfg;
77 * Handle for controlling the daemon.
79 struct GNUNET_TESTING_Daemon *daemon;
82 * Linked list of peer connections (simply indexes of PeerGroup)
83 * FIXME: Question, store pointer or integer? Pointer for now...
85 struct PeerConnection *connected_peers;
90 * Data we keep per host.
100 * Lowest port that we have not yet used
108 * Handle to a group of GNUnet peers.
110 struct GNUNET_TESTING_PeerGroup
115 struct GNUNET_SCHEDULER_Handle *sched;
118 * Configuration template.
120 const struct GNUNET_CONFIGURATION_Handle *cfg;
123 * Function to call on each started daemon.
125 GNUNET_TESTING_NotifyDaemonRunning cb;
133 * Function to call on each topology connection created
135 GNUNET_TESTING_NotifyConnection notify_connection;
138 * Callback for notify_connection
140 void *notify_connection_cls;
143 * NULL-terminated array of information about
146 struct HostData *hosts;
149 * Array of "total" peers.
151 struct PeerData *peers;
154 * Number of peers in this group.
163 struct GNUNET_CONFIGURATION_Handle *ret;
168 * Function to iterate over options. Copies
169 * the options to the target configuration,
170 * updating PORT values as needed.
173 * @param section name of the section
174 * @param option name of the option
175 * @param value value of the option
178 update_config (void *cls,
179 const char *section, const char *option, const char *value)
181 struct UpdateContext *ctx = cls;
185 if ((0 == strcmp (option, "PORT")) && (1 == sscanf (value, "%u", &ival)))
187 GNUNET_snprintf (cval, sizeof (cval), "%u", ctx->nport++);
190 GNUNET_CONFIGURATION_set_value_string (ctx->ret, section, option, value);
195 * Create a new configuration using the given configuration
196 * as a template; however, each PORT in the existing cfg
197 * must be renumbered by incrementing "*port". If we run
198 * out of "*port" numbers, return NULL.
200 * @param cfg template configuration
201 * @param port port numbers to use, update to reflect
202 * port numbers that were used
203 * @return new configuration, NULL on error
205 static struct GNUNET_CONFIGURATION_Handle *
206 make_config (const struct GNUNET_CONFIGURATION_Handle *cfg, uint16_t * port)
208 struct UpdateContext uc;
213 uc.ret = GNUNET_CONFIGURATION_create ();
214 GNUNET_CONFIGURATION_iterate (cfg, &update_config, &uc);
215 if (uc.nport >= HIGH_PORT)
218 GNUNET_CONFIGURATION_destroy (uc.ret);
221 *port = (uint16_t) uc.nport;
226 * Add entries to the peers connected list
228 * @param pg the peer group we are working with
229 * @param first index of the first peer
230 * @param second index of the second peer
232 * @return the number of connections added (can be 0 1 or 2)
234 * FIXME: add both, or only add one?
235 * - if both are added, then we have to keep track
236 * when connecting so we don't double connect
237 * - if only one is added, we need to iterate over
238 * both lists to find out if connection already exists
239 * - having both allows the whitelisting/friend file
240 * creation to be easier
242 * -- For now, add both, we have to iterate over each to
243 * check for duplicates anyways, so we'll take the performance
244 * hit assuming we don't have __too__ many connections
248 add_connections(struct GNUNET_TESTING_PeerGroup *pg, unsigned int first, unsigned int second)
251 struct PeerConnection *first_iter;
252 struct PeerConnection *second_iter;
255 struct PeerConnection *new_first;
256 struct PeerConnection *new_second;
258 first_iter = pg->peers[first].connected_peers;
259 add_first = GNUNET_YES;
260 while (first_iter != NULL)
262 if (first_iter->daemon == pg->peers[second].daemon)
263 add_first = GNUNET_NO;
264 first_iter = first_iter->next;
267 second_iter = pg->peers[second].connected_peers;
268 add_second = GNUNET_YES;
269 while (second_iter != NULL)
271 if (second_iter->daemon == pg->peers[first].daemon)
272 add_second = GNUNET_NO;
273 second_iter = second_iter->next;
279 new_first = GNUNET_malloc(sizeof(struct PeerConnection));
280 new_first->daemon = pg->peers[second].daemon;
281 new_first->next = pg->peers[first].connected_peers;
282 pg->peers[first].connected_peers = new_first;
288 new_second = GNUNET_malloc(sizeof(struct PeerConnection));
289 new_second->daemon = pg->peers[first].daemon;
290 new_second->next = pg->peers[second].connected_peers;
291 pg->peers[second].connected_peers = new_second;
299 create_clique (struct GNUNET_TESTING_PeerGroup *pg)
301 unsigned int outer_count;
302 unsigned int inner_count;
303 int connect_attempts;
305 connect_attempts = 0;
307 for (outer_count = 0; outer_count < pg->total - 1; outer_count++)
309 for (inner_count = outer_count + 1; inner_count < pg->total;
313 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
314 "Connecting peer %d to peer %d\n",
315 outer_count, inner_count);
317 connect_attempts += add_connections(pg, outer_count, inner_count);
318 /*GNUNET_TESTING_daemons_connect (pg->peers[outer_count].daemon,
319 pg->peers[inner_count].daemon,
321 pg->notify_connection,
322 pg->notify_connection_cls);*/
326 return connect_attempts;
331 * Create the friend files based on the PeerConnection's
332 * of each peer in the peer group, and copy the files
333 * to the appropriate place
335 * @param pg the peer group we are dealing with
338 create_and_copy_friend_files (struct GNUNET_TESTING_PeerGroup *pg)
340 FILE *temp_friend_handle;
341 unsigned int pg_iter;
342 struct PeerConnection *connection_iter;
343 struct GNUNET_CRYPTO_HashAsciiEncoded peer_enc;
344 char *temp_service_path;
347 struct GNUNET_PeerIdentity *temppeer;
350 for (pg_iter = 0; pg_iter < pg->total; pg_iter++)
352 mytemp = GNUNET_DISK_mktemp("friends");
353 temp_friend_handle = fopen (mytemp, "wt");
354 connection_iter = pg->peers[pg_iter].connected_peers;
355 while (connection_iter != NULL)
357 temppeer = &connection_iter->daemon->id;
358 GNUNET_CRYPTO_hash_to_enc(&temppeer->hashPubKey, &peer_enc);
359 fprintf(temp_friend_handle, "%s\n", (char *)&peer_enc);
360 connection_iter = connection_iter->next;
363 fclose(temp_friend_handle);
365 GNUNET_CONFIGURATION_get_value_string(pg->peers[pg_iter].daemon->cfg, "PATHS", "SERVICEHOME", &temp_service_path);
367 if (temp_service_path == NULL)
369 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
370 _("No SERVICEHOME specified in peer configuration, can't copy friends file!\n"));
371 fclose(temp_friend_handle);
376 if (pg->peers[pg_iter].daemon->hostname == NULL) /* Local, just copy the file */
378 GNUNET_asprintf (&arg, "%s/friends", temp_service_path);
379 pid = GNUNET_OS_start_process ("mv",
380 "mv", mytemp, arg, NULL);
382 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
383 _("Copying file with command cp %s %s\n"), mytemp, arg);
387 else /* Remote, scp the file to the correct place */
389 if (NULL != pg->peers[pg_iter].daemon->username)
390 GNUNET_asprintf (&arg, "%s@%s:%s/friends", pg->peers[pg_iter].daemon->username, pg->peers[pg_iter].daemon->hostname, temp_service_path);
392 GNUNET_asprintf (&arg, "%s:%s/friends", pg->peers[pg_iter].daemon->hostname, temp_service_path);
393 pid = GNUNET_OS_start_process ("scp",
394 "scp", mytemp, arg, NULL);
396 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
397 _("Copying file with command scp %s %s\n"), mytemp, arg);
408 * Connect the topology as specified by the PeerConnection's
409 * of each peer in the peer group
411 * @param pg the peer group we are dealing with
414 connect_topology (struct GNUNET_TESTING_PeerGroup *pg)
416 unsigned int pg_iter;
417 struct PeerConnection *connection_iter;
419 for (pg_iter = 0; pg_iter < pg->total; pg_iter++)
421 connection_iter = pg->peers[pg_iter].connected_peers;
422 while (connection_iter != NULL)
424 GNUNET_TESTING_daemons_connect (pg->peers[pg_iter].daemon,
425 connection_iter->daemon,
427 pg->notify_connection,
428 pg->notify_connection_cls);
429 connection_iter = connection_iter->next;
436 * Takes a peer group and attempts to create a topology based on the
437 * one specified in the configuration file. Returns the number of connections
438 * that will attempt to be created, but this will happen asynchronously(?) so
439 * the caller will have to keep track (via the callback) of whether or not
440 * the connection actually happened.
442 * @param pg the peer group struct representing the running peers
446 GNUNET_TESTING_create_topology (struct GNUNET_TESTING_PeerGroup *pg)
448 unsigned long long topology_num;
451 GNUNET_assert (pg->notify_connection != NULL);
454 GNUNET_CONFIGURATION_get_value_number (pg->cfg, "testing", "topology",
457 switch (topology_num)
459 case GNUNET_TESTING_TOPOLOGY_CLIQUE:
461 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
462 _("Creating clique topology (may take a bit!)\n"));
463 ret = create_clique (pg);
465 case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD:
466 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
467 _("Creating small world topology (may take a bit!)\n"));
471 GNUNET_REMOTE_connect_small_world_ring (&totalConnections,
473 list_as_array, dotOutFile,
474 percentage, logNModifier);
477 case GNUNET_TESTING_TOPOLOGY_RING:
479 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
480 _("Creating ring topology (may take a bit!)\n"));
483 ret = GNUNET_REMOTE_connect_ring (&totalConnections, head, dotOutFile);
487 case GNUNET_TESTING_TOPOLOGY_2D_TORUS:
489 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
490 _("Creating 2d torus topology (may take a bit!)\n"));
494 GNUNET_REMOTE_connect_2d_torus (&totalConnections, number_of_daemons,
495 list_as_array, dotOutFile);
499 case GNUNET_TESTING_TOPOLOGY_ERDOS_RENYI:
501 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502 _("Creating Erdos-Renyi topology (may take a bit!)\n"));
505 GNUNET_REMOTE_connect_erdos_renyi (&totalConnections, percentage,
510 case GNUNET_TESTING_TOPOLOGY_INTERNAT:
512 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
513 _("Creating InterNAT topology (may take a bit!)\n"));
517 GNUNET_REMOTE_connect_nated_internet (&totalConnections, percentage,
518 number_of_daemons, head,
523 case GNUNET_TESTING_TOPOLOGY_NONE:
531 if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (pg->cfg, "TESTING", "F2F"))
532 create_and_copy_friend_files(pg);
534 connect_topology(pg);
538 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
539 _("No topology specified, was one intended?\n"));
546 * Start count gnunetd processes with the same set of transports and
547 * applications. The port numbers (any option called "PORT") will be
548 * adjusted to ensure that no two peers running on the same system
549 * have the same port(s) in their respective configurations.
551 * @param sched scheduler to use
552 * @param cfg configuration template to use
553 * @param total number of daemons to start
554 * @param cb function to call on each daemon that was started
555 * @param cb_cls closure for cb
556 * @param connect_callback function to call each time two hosts are connected
557 * @param connect_callback_cls closure for connect_callback
558 * @param hostnames space-separated list of hostnames to use; can be NULL (to run
559 * everything on localhost).
560 * @return NULL on error, otherwise handle to control peer group
562 struct GNUNET_TESTING_PeerGroup *
563 GNUNET_TESTING_daemons_start (struct GNUNET_SCHEDULER_Handle *sched,
564 const struct GNUNET_CONFIGURATION_Handle *cfg,
566 GNUNET_TESTING_NotifyDaemonRunning cb,
568 GNUNET_TESTING_NotifyConnection
569 connect_callback, void *connect_callback_cls,
570 const char *hostnames)
572 struct GNUNET_TESTING_PeerGroup *pg;
576 const char *hostname;
577 char *baseservicehome;
578 char *newservicehome;
580 struct GNUNET_CONFIGURATION_Handle *pcfg;
582 unsigned int hostcnt;
591 pg = GNUNET_malloc (sizeof (struct GNUNET_TESTING_PeerGroup));
596 pg->notify_connection = connect_callback;
597 pg->notify_connection_cls = connect_callback_cls;
599 pg->peers = GNUNET_malloc (total * sizeof (struct PeerData));
600 if (NULL != hostnames)
603 /* skip leading spaces */
604 while ((0 != *hostnames) && (isspace (*hostnames)))
607 while ('\0' != *rpos)
613 pg->hosts = GNUNET_malloc (off * sizeof (struct HostData));
615 start = GNUNET_strdup (hostnames);
622 if (strlen (start) > 0)
624 pg->hosts[off].minport = LOW_PORT;
625 pg->hosts[off++].hostname = start;
631 if (strlen (start) > 0)
633 pg->hosts[off].minport = LOW_PORT;
634 pg->hosts[off++].hostname = start;
639 GNUNET_free (pg->hosts);
643 minport = 0; /* make gcc happy */
650 for (off = 0; off < total; off++)
654 hostname = pg->hosts[off % hostcnt].hostname;
655 pcfg = make_config (cfg, &pg->hosts[off % hostcnt].minport);
660 pcfg = make_config (cfg, &minport);
664 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
666 ("Could not create configuration for peer number %u on `%s'!\n"),
667 off, hostname == NULL ? "localhost" : hostname);
672 GNUNET_CONFIGURATION_get_value_string (pcfg, "PATHS", "SERVICEHOME",
675 tempsize = snprintf (NULL, 0, "%s/%d/", baseservicehome, off) + 1;
676 newservicehome = GNUNET_malloc (tempsize);
677 snprintf (newservicehome, tempsize, "%s/%d/", baseservicehome, off);
681 tmpdir = getenv ("TMPDIR");
682 tmpdir = tmpdir ? tmpdir : "/tmp";
683 tempsize = snprintf (NULL, 0, "%s/%s/%d/", tmpdir, "gnunet-testing-test-test", off) + 1;
684 newservicehome = GNUNET_malloc (tempsize);
685 snprintf (newservicehome, tempsize, "%s/%d/",
686 "/tmp/gnunet-testing-test-test", off);
688 GNUNET_CONFIGURATION_set_value_string (pcfg,
690 "SERVICEHOME", newservicehome);
692 pg->peers[off].cfg = pcfg;
693 pg->peers[off].daemon = GNUNET_TESTING_daemon_start (sched,
697 if (NULL == pg->peers[off].daemon)
698 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
699 _("Could not start peer number %u!\n"), off);
705 * Get a daemon by number, so callers don't have to do nasty
706 * offsetting operation.
708 struct GNUNET_TESTING_Daemon *
709 GNUNET_TESTING_daemon_get (struct GNUNET_TESTING_PeerGroup *pg, unsigned int position)
711 if (position < pg->total)
712 return pg->peers[position].daemon;
718 * Shutdown all peers started in the given group.
720 * @param pg handle to the peer group
723 GNUNET_TESTING_daemons_stop (struct GNUNET_TESTING_PeerGroup *pg)
727 for (off = 0; off < pg->total; off++)
729 /* FIXME: should we wait for our
730 continuations to be called here? This
731 would require us to take a continuation
734 if (NULL != pg->peers[off].daemon)
735 GNUNET_TESTING_daemon_stop (pg->peers[off].daemon, NULL, NULL);
736 if (NULL != pg->peers[off].cfg)
737 GNUNET_CONFIGURATION_destroy (pg->peers[off].cfg);
739 GNUNET_free (pg->peers);
740 if (NULL != pg->hosts)
742 GNUNET_free (pg->hosts[0].hostname);
743 GNUNET_free (pg->hosts);
749 /* end of testing_group.c */