/* This file is part of GNUnet (C) 2008, 2009 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file testing/testing_group.c * @brief convenience API for writing testcases for GNUnet * @author Nathan Evans * @author Christian Grothoff */ #include "platform.h" #include "gnunet_constants.h" #include "gnunet_arm_service.h" #include "gnunet_testing_lib.h" #include "gnunet_core_service.h" #define VERBOSE_TESTING GNUNET_NO #define VERBOSE_TOPOLOGY GNUNET_NO #define DEBUG_CHURN GNUNET_EXTRA_LOGGING #define USE_START_HELPER GNUNET_YES #define OLD 1 /* Before connecting peers, send all of the HELLOs */ #define USE_SEND_HELLOS GNUNET_NO #define TOPOLOGY_HACK GNUNET_YES /** * Lowest port used for GNUnet testing. Should be high enough to not * conflict with other applications running on the hosts but be low * enough to not conflict with client-ports (typically starting around * 32k). */ #define LOW_PORT 12000 /** * Highest port used for GNUnet testing. Should be low enough to not * conflict with the port range for "local" ports (client apps; see * /proc/sys/net/ipv4/ip_local_port_range on Linux for example). */ #define HIGH_PORT 56000 /* Maximum time to delay connect attempt */ #define MAX_CONNECT_DELAY 300 /** * Which list of peers do we need to modify? */ enum PeerLists { /** Modify allowed peers */ ALLOWED, /** Modify connect peers */ CONNECT, /** Modify blacklist peers */ BLACKLIST, /** Modify workingset peers */ WORKING_SET }; /** * Prototype of a function called whenever two peers would be connected * in a certain topology. */ typedef unsigned int (*GNUNET_TESTING_ConnectionProcessor) (struct GNUNET_TESTING_PeerGroup * pg, unsigned int first, unsigned int second, enum PeerLists list, unsigned int check); /** * Context for handling churning a peer group */ struct ChurnContext { /** * The peergroup we are dealing with. */ struct GNUNET_TESTING_PeerGroup *pg; /** * Name of the service to churn on/off, NULL * to churn entire peer. */ char *service; /** * Callback used to notify of churning finished */ GNUNET_TESTING_NotifyCompletion cb; /** * Closure for callback */ void *cb_cls; /** * Number of peers that still need to be started */ unsigned int num_to_start; /** * Number of peers that still need to be stopped */ unsigned int num_to_stop; /** * Number of peers that failed to start */ unsigned int num_failed_start; /** * Number of peers that failed to stop */ unsigned int num_failed_stop; }; struct RestartContext { /** * The group of peers being restarted */ struct GNUNET_TESTING_PeerGroup *peer_group; /** * How many peers have been restarted thus far */ unsigned int peers_restarted; /** * How many peers got an error when restarting */ unsigned int peers_restart_failed; /** * The function to call once all peers have been restarted */ GNUNET_TESTING_NotifyCompletion callback; /** * Closure for callback function */ void *callback_cls; }; struct SendHelloContext { /** * Global handle to the peer group. */ struct GNUNET_TESTING_PeerGroup *pg; /** * The data about this specific peer. */ struct PeerData *peer; /** * The next HELLO that needs sent to this peer. */ struct PeerConnection *peer_pos; /** * Are we connected to CORE yet? */ unsigned int core_ready; /** * How many attempts should we make for failed connections? */ unsigned int connect_attempts; /** * Task for scheduling core connect requests to be sent. */ GNUNET_SCHEDULER_TaskIdentifier core_connect_task; }; struct ShutdownContext { struct GNUNET_TESTING_PeerGroup *pg; /** * Total peers to wait for */ unsigned int total_peers; /** * Number of peers successfully shut down */ unsigned int peers_down; /** * Number of peers failed to shut down */ unsigned int peers_failed; /** * Number of peers we have started shutting * down. If too many, wait on them. */ unsigned int outstanding; /** * Timeout for shutdown. */ struct GNUNET_TIME_Relative timeout; /** * Callback to call when all peers either * shutdown or failed to shutdown */ GNUNET_TESTING_NotifyCompletion cb; /** * Closure for cb */ void *cb_cls; /** * Should we delete all of the files from the peers? */ int delete_files; }; /** * Individual shutdown context for a particular peer. */ struct PeerShutdownContext { /** * Pointer to the high level shutdown context. */ struct ShutdownContext *shutdown_ctx; /** * The daemon handle for the peer to shut down. */ struct GNUNET_TESTING_Daemon *daemon; }; /** * Individual shutdown context for a particular peer. */ struct PeerRestartContext { /** * Pointer to the high level restart context. */ struct ChurnRestartContext *churn_restart_ctx; /** * The daemon handle for the peer to shut down. */ struct GNUNET_TESTING_Daemon *daemon; }; struct ServiceStartContext { struct GNUNET_TESTING_PeerGroup *pg; unsigned int remaining; GNUNET_TESTING_NotifyCompletion cb; unsigned int outstanding; char *service; struct GNUNET_TIME_Relative timeout; void *cb_cls; }; /** * Individual shutdown context for a particular peer. */ struct PeerServiceStartContext { /** * Pointer to the high level start context. */ struct ServiceStartContext *start_ctx; /** * The daemon handle for the peer to start the service on. */ struct GNUNET_TESTING_Daemon *daemon; }; struct CreateTopologyContext { /** * Function to call with number of connections */ GNUNET_TESTING_NotifyConnections cont; /** * Closure for connection notification */ void *cls; }; enum States { /** Waiting to read number of peers */ NUM_PEERS, /** Should find next peer index */ PEER_INDEX, /** Should find colon */ COLON, /** Should read other peer index, space, or endline */ OTHER_PEER_INDEX }; #if OLD struct PeerConnection { /** * Doubly Linked list */ struct PeerConnection *prev; /* * Doubly Linked list */ struct PeerConnection *next; /* * Index of daemon in pg->peers */ uint32_t index; }; #endif struct InternalStartContext { /** * Pointer to peerdata */ struct PeerData *peer; /** * Timeout for peer startup */ struct GNUNET_TIME_Relative timeout; /** * Client callback for hostkey notification */ GNUNET_TESTING_NotifyHostkeyCreated hostkey_callback; /** * Closure for hostkey_callback */ void *hostkey_cls; /** * Client callback for peer start notification */ GNUNET_TESTING_NotifyDaemonRunning start_cb; /** * Closure for cb */ void *start_cb_cls; /** * Hostname, where to start the peer */ const char *hostname; /** * Username to use when connecting to the * host via ssh. */ const char *username; /** * Pointer to starting memory location of a hostkey */ const char *hostkey; /** * Port to use for ssh. */ uint16_t sshport; }; struct ChurnRestartContext { /** * PeerGroup that we are working with. */ struct GNUNET_TESTING_PeerGroup *pg; /** * Number of restarts currently in flight. */ unsigned int outstanding; /** * Handle to the underlying churn context. */ struct ChurnContext *churn_ctx; /** * How long to allow the operation to take. */ struct GNUNET_TIME_Relative timeout; }; struct OutstandingSSH { struct OutstandingSSH *next; struct OutstandingSSH *prev; /** * Number of current ssh connections. */ uint32_t outstanding; /** * The hostname of this peer. */ const char *hostname; }; /** * Data we keep per peer. */ struct PeerData { /** * (Initial) configuration of the host. * (initial because clients could change * it and we would not know about those * updates). */ struct GNUNET_CONFIGURATION_Handle *cfg; /** * Handle for controlling the daemon. */ struct GNUNET_TESTING_Daemon *daemon; /** * The peergroup this peer belongs to. */ struct GNUNET_TESTING_PeerGroup *pg; #if OLD /** * Linked list of allowed peer connections. */ struct PeerConnection *allowed_peers_head; /** * Linked list of allowed peer connections. */ struct PeerConnection *allowed_peers_tail; /** * Linked list of blacklisted peer connections. */ struct PeerConnection *blacklisted_peers_head; /** * Linked list of blacklisted peer connections. */ struct PeerConnection *blacklisted_peers_tail; /** * Linked list of connect peer connections. */ struct PeerConnection *connect_peers_head; /** * Linked list of connect peer connections. */ struct PeerConnection *connect_peers_tail; /** * Linked list of connect peer connections. */ struct PeerConnection *connect_peers_working_set_head; /** * Linked list of connect peer connections. */ struct PeerConnection *connect_peers_working_set_tail; #else /** * Hash map of allowed peer connections (F2F created topology) */ struct GNUNET_CONTAINER_MultiHashMap *allowed_peers; /** * Hash map of blacklisted peers */ struct GNUNET_CONTAINER_MultiHashMap *blacklisted_peers; /** * Hash map of peer connections */ struct GNUNET_CONTAINER_MultiHashMap *connect_peers; /** * Temporary hash map of peer connections */ struct GNUNET_CONTAINER_MultiHashMap *connect_peers_working_set; #endif /** * Temporary variable for topology creation, should be reset before * creating any topology so the count is valid once finished. */ int num_connections; /** * Context to keep track of peers being started, to * stagger hostkey generation and peer startup. */ struct InternalStartContext internal_context; /** * Task ID for the queued internal_continue_startup task */ GNUNET_SCHEDULER_TaskIdentifier startup_task; }; /** * Linked list of per-host data. */ struct HostData { /** * Name of the host. */ char *hostname; /** * SSH username to use when connecting to this host. */ char *username; /** * SSH port to use when connecting to this host. */ uint16_t sshport; /** * Lowest port that we have not yet used * for GNUnet. */ uint16_t minport; }; struct TopologyIterateContext { /** * The peergroup we are working with. */ struct GNUNET_TESTING_PeerGroup *pg; /** * Callback for notifying of two connected peers. */ GNUNET_TESTING_NotifyTopology topology_cb; /** * Closure for topology_cb */ void *cls; /** * Number of peers currently connected to. */ unsigned int connected; /** * Number of peers we have finished iterating. */ unsigned int completed; /** * Number of peers total. */ unsigned int total; }; struct StatsIterateContext { /** * The peergroup that we are dealing with. */ struct GNUNET_TESTING_PeerGroup *pg; /** * Continuation to call once all stats information has been retrieved. */ GNUNET_STATISTICS_Callback cont; /** * Proc function to call on each value received. */ GNUNET_TESTING_STATISTICS_Iterator proc; /** * Closure for topology_cb */ void *cls; /** * Number of peers currently connected to. */ unsigned int connected; /** * Number of peers we have finished iterating. */ unsigned int completed; /** * Number of peers total. */ unsigned int total; }; struct CoreContext { void *iter_context; struct GNUNET_TESTING_Daemon *daemon; }; struct StatsCoreContext { void *iter_context; struct GNUNET_TESTING_Daemon *daemon; /** * Handle to the statistics service. */ struct GNUNET_STATISTICS_Handle *stats_handle; /** * Handle for getting statistics. */ struct GNUNET_STATISTICS_GetHandle *stats_get_handle; }; struct ConnectTopologyContext { /** * How many connections are left to create. */ unsigned int remaining_connections; /** * Handle to group of peers. */ struct GNUNET_TESTING_PeerGroup *pg; /** * How long to try this connection before timing out. */ struct GNUNET_TIME_Relative connect_timeout; /** * How many times to retry connecting the two peers. */ unsigned int connect_attempts; /** * Temp value set for each iteration. */ //struct PeerData *first; /** * Notification that all peers are connected. */ GNUNET_TESTING_NotifyCompletion notify_connections_done; /** * Closure for notify. */ void *notify_cls; }; struct ConnectContext; /** * Handle to a group of GNUnet peers. */ struct GNUNET_TESTING_PeerGroup { /** * Configuration template. */ const struct GNUNET_CONFIGURATION_Handle *cfg; struct ConnectContext *cc_head; struct ConnectContext *cc_tail; /** * Function to call on each started daemon. */ //GNUNET_TESTING_NotifyDaemonRunning cb; /** * Closure for cb. */ //void *cb_cls; /* * Function to call on each topology connection created */ GNUNET_TESTING_NotifyConnection notify_connection; /* * Callback for notify_connection */ void *notify_connection_cls; /** * Array of information about hosts. */ struct HostData *hosts; /** * Number of hosts (size of HostData) */ unsigned int num_hosts; /** * Array of "total" peers. */ struct PeerData *peers; /** * Number of peers in this group. */ unsigned int total; /** * At what time should we fail the peer startup process? */ struct GNUNET_TIME_Absolute max_timeout; /** * How many peers are being started right now? */ unsigned int starting; /** * How many peers have already been started? */ unsigned int started; /** * Number of possible connections to peers * at a time. */ unsigned int max_outstanding_connections; /** * Number of ssh connections to peers (max). */ unsigned int max_concurrent_ssh; /** * Number of connects we are waiting on, allows us to rate limit * connect attempts. */ unsigned int outstanding_connects; /** * Number of HELLOs we have yet to send. */ unsigned int remaining_hellos; /** * How many connects have already been scheduled? */ unsigned int total_connects_scheduled; /** * Hostkeys loaded from a file. */ char *hostkey_data; /** * Head of DLL to keep track of the number of outstanding * ssh connections per peer. */ struct OutstandingSSH *ssh_head; /** * Tail of DLL to keep track of the number of outstanding * ssh connections per peer. */ struct OutstandingSSH *ssh_tail; /** * Stop scheduling peers connecting. */ unsigned int stop_connects; /** * Connection context for peer group. */ struct ConnectTopologyContext ct_ctx; }; struct UpdateContext { /** * The altered configuration. */ struct GNUNET_CONFIGURATION_Handle *ret; /** * The original configuration to alter. */ const struct GNUNET_CONFIGURATION_Handle *orig; /** * The hostname that this peer will run on. */ const char *hostname; /** * The next possible port to assign. */ unsigned int nport; /** * Unique number for unix domain sockets. */ unsigned int upnum; /** * Unique number for this peer/host to offset * things that are grouped by host. */ unsigned int fdnum; }; struct ConnectContext { struct ConnectContext *next; struct ConnectContext *prev; /** * Index of peer to connect second to. */ uint32_t first_index; /** * Index of peer to connect first to. */ uint32_t second_index; /** * Task associated with the attempt to connect. */ GNUNET_SCHEDULER_TaskIdentifier task; /** * Context in 'testing.c', to cancel connection attempt. */ struct GNUNET_TESTING_ConnectContext *cc; /** * Higher level topology connection context. */ struct ConnectTopologyContext *ct_ctx; /** * Whether this connection has been accounted for in the schedule_connect call. */ int counted; }; struct UnblacklistContext { /** * The peergroup */ struct GNUNET_TESTING_PeerGroup *pg; /** * uid of the first peer */ uint32_t first_uid; }; struct RandomContext { /** * The peergroup */ struct GNUNET_TESTING_PeerGroup *pg; /** * uid of the first peer */ uint32_t first_uid; /** * Peer data for first peer. */ struct PeerData *first; /** * Random percentage to use */ double percentage; }; struct MinimumContext { /** * The peergroup */ struct GNUNET_TESTING_PeerGroup *pg; /** * uid of the first peer */ uint32_t first_uid; /** * Peer data for first peer. */ struct PeerData *first; /** * Number of conns per peer */ unsigned int num_to_add; /** * Permuted array of all possible connections. Only add the Nth * peer if it's in the Nth position. */ unsigned int *pg_array; /** * What number is the current element we are iterating over? */ unsigned int current; }; struct DFSContext { /** * The peergroup */ struct GNUNET_TESTING_PeerGroup *pg; /** * uid of the first peer */ uint32_t first_uid; /** * uid of the second peer */ uint32_t second_uid; /** * Peer data for first peer. */ struct PeerData *first; /** * Which peer has been chosen as the one to add? */ unsigned int chosen; /** * What number is the current element we are iterating over? */ unsigned int current; }; /** * Simple struct to keep track of progress, and print a * nice little percentage meter for long running tasks. */ struct ProgressMeter { unsigned int total; unsigned int modnum; unsigned int dotnum; unsigned int completed; int print; char *startup_string; }; #if !OLD /** * Convert unique ID to hash code. * * @param uid unique ID to convert * @param hash set to uid (extended with zeros) */ static void hash_from_uid (uint32_t uid, GNUNET_HashCode * hash) { memset (hash, 0, sizeof (GNUNET_HashCode)); *((uint32_t *) hash) = uid; } /** * Convert hash code to unique ID. * * @param uid unique ID to convert * @param hash set to uid (extended with zeros) */ static void uid_from_hash (const GNUNET_HashCode * hash, uint32_t * uid) { memcpy (uid, hash, sizeof (uint32_t)); } #endif #if USE_SEND_HELLOS static struct GNUNET_CORE_MessageHandler no_handlers[] = { {NULL, 0, 0} }; #endif /** * Create a meter to keep track of the progress of some task. * * @param total the total number of items to complete * @param start_string a string to prefix the meter with (if printing) * @param print GNUNET_YES to print the meter, GNUNET_NO to count * internally only * * @return the progress meter */ static struct ProgressMeter * create_meter (unsigned int total, char *start_string, int print) { struct ProgressMeter *ret; ret = GNUNET_malloc (sizeof (struct ProgressMeter)); ret->print = print; ret->total = total; ret->modnum = total / 4; if (ret->modnum == 0) /* Divide by zero check */ ret->modnum = 1; ret->dotnum = (total / 50) + 1; if (start_string != NULL) ret->startup_string = GNUNET_strdup (start_string); else ret->startup_string = GNUNET_strdup (""); return ret; } /** * Update progress meter (increment by one). * * @param meter the meter to update and print info for * * @return GNUNET_YES if called the total requested, * GNUNET_NO if more items expected */ static int update_meter (struct ProgressMeter *meter) { if (meter->print == GNUNET_YES) { if (meter->completed % meter->modnum == 0) { if (meter->completed == 0) { FPRINTF (stdout, "%sProgress: [0%%", meter->startup_string); } else FPRINTF (stdout, "%d%%", (int) (((float) meter->completed / meter->total) * 100)); } else if (meter->completed % meter->dotnum == 0) FPRINTF (stdout, "%s", "."); if (meter->completed + 1 == meter->total) FPRINTF (stdout, "%d%%]\n", 100); fflush (stdout); } meter->completed++; if (meter->completed == meter->total) return GNUNET_YES; if (meter->completed > meter->total) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Progress meter overflow!!\n"); return GNUNET_NO; } /** * Reset progress meter. * * @param meter the meter to reset * * @return GNUNET_YES if meter reset, * GNUNET_SYSERR on error */ static int reset_meter (struct ProgressMeter *meter) { if (meter == NULL) return GNUNET_SYSERR; meter->completed = 0; return GNUNET_YES; } /** * Release resources for meter * * @param meter the meter to free */ static void free_meter (struct ProgressMeter *meter) { GNUNET_free_non_null (meter->startup_string); GNUNET_free (meter); } /** * Get a topology from a string input. * * @param topology where to write the retrieved topology * @param topology_string The string to attempt to * get a configuration value from * @return GNUNET_YES if topology string matched a * known topology, GNUNET_NO if not */ int GNUNET_TESTING_topology_get (enum GNUNET_TESTING_Topology *topology, const char *topology_string) { /** * Strings representing topologies in enum */ static const char *topology_strings[] = { /** * A clique (everyone connected to everyone else). */ "CLIQUE", /** * Small-world network (2d torus plus random links). */ "SMALL_WORLD", /** * Small-world network (ring plus random links). */ "SMALL_WORLD_RING", /** * Ring topology. */ "RING", /** * 2-d torus. */ "2D_TORUS", /** * Random graph. */ "ERDOS_RENYI", /** * Certain percentage of peers are unable to communicate directly * replicating NAT conditions */ "INTERNAT", /** * Scale free topology. */ "SCALE_FREE", /** * Straight line topology. */ "LINE", /** * All peers are disconnected. */ "NONE", /** * Read the topology from a file. */ "FROM_FILE", NULL }; int curr = 0; if (topology_string == NULL) return GNUNET_NO; while (topology_strings[curr] != NULL) { if (strcasecmp (topology_strings[curr], topology_string) == 0) { *topology = curr; return GNUNET_YES; } curr++; } *topology = GNUNET_TESTING_TOPOLOGY_NONE; return GNUNET_NO; } /** * Get connect topology option from string input. * * @param topology_option where to write the retrieved topology * @param topology_string The string to attempt to * get a configuration value from * @return GNUNET_YES if string matched a known * topology option, GNUNET_NO if not */ int GNUNET_TESTING_topology_option_get (enum GNUNET_TESTING_TopologyOption *topology_option, const char *topology_string) { /** * Options for connecting a topology as strings. */ static const char *topology_option_strings[] = { /** * Try to connect all peers specified in the topology. */ "CONNECT_ALL", /** * Choose a random subset of connections to create. */ "CONNECT_RANDOM_SUBSET", /** * Create at least X connections for each peer. */ "CONNECT_MINIMUM", /** * Using a depth first search, create one connection * per peer. If any are missed (graph disconnected) * start over at those peers until all have at least one * connection. */ "CONNECT_DFS", /** * Find the N closest peers to each allowed peer in the * topology and make sure a connection to those peers * exists in the connect topology. */ "CONNECT_CLOSEST", /** * No options specified. */ "CONNECT_NONE", NULL }; int curr = 0; if (topology_string == NULL) return GNUNET_NO; while (NULL != topology_option_strings[curr]) { if (strcasecmp (topology_option_strings[curr], topology_string) == 0) { *topology_option = curr; return GNUNET_YES; } curr++; } *topology_option = GNUNET_TESTING_TOPOLOGY_OPTION_NONE; return GNUNET_NO; } /** * Function to iterate over options. Copies * the options to the target configuration, * updating PORT values as needed. * * @param cls closure * @param section name of the section * @param option name of the option * @param value value of the option */ static void update_config (void *cls, const char *section, const char *option, const char *value) { struct UpdateContext *ctx = cls; unsigned int ival; char cval[12]; char uval[128]; char *single_variable; char *per_host_variable; unsigned long long num_per_host; GNUNET_asprintf (&single_variable, "single_%s_per_host", section); GNUNET_asprintf (&per_host_variable, "num_%s_per_host", section); if ((0 == strcmp (option, "PORT")) && (1 == sscanf (value, "%u", &ival))) { if ((ival != 0) && (GNUNET_YES != GNUNET_CONFIGURATION_get_value_yesno (ctx->orig, "testing", single_variable))) { GNUNET_snprintf (cval, sizeof (cval), "%u", ctx->nport++); value = cval; } else if ((ival != 0) && (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (ctx->orig, "testing", single_variable)) && GNUNET_CONFIGURATION_get_value_number (ctx->orig, "testing", per_host_variable, &num_per_host)) { GNUNET_snprintf (cval, sizeof (cval), "%u", ival + ctx->fdnum % num_per_host); value = cval; } /* FIXME: REMOVE FOREVER HACK HACK HACK */ if (0 == strcasecmp (section, "transport-tcp")) GNUNET_CONFIGURATION_set_value_string (ctx->ret, section, "ADVERTISED_PORT", value); } if (0 == strcmp (option, "UNIXPATH")) { if (GNUNET_YES != GNUNET_CONFIGURATION_get_value_yesno (ctx->orig, "testing", single_variable)) { GNUNET_snprintf (uval, sizeof (uval), "/tmp/test-service-%s-%u", section, ctx->upnum++); value = uval; } else if ((GNUNET_YES == GNUNET_CONFIGURATION_get_value_number (ctx->orig, "testing", per_host_variable, &num_per_host)) && (num_per_host > 0)) { GNUNET_snprintf (uval, sizeof (uval), "/tmp/test-service-%s-%u", section, ctx->fdnum % num_per_host); value = uval; } } if ((0 == strcmp (option, "HOSTNAME")) && (ctx->hostname != NULL)) { value = ctx->hostname; } GNUNET_free (single_variable); GNUNET_free (per_host_variable); GNUNET_CONFIGURATION_set_value_string (ctx->ret, section, option, value); } /** * Create a new configuration using the given configuration * as a template; however, each PORT in the existing cfg * must be renumbered by incrementing "*port". If we run * out of "*port" numbers, return NULL. * * @param cfg template configuration * @param off the current peer offset * @param port port numbers to use, update to reflect * port numbers that were used * @param upnum number to make unix domain socket names unique * @param hostname hostname of the controlling host, to allow control connections from * @param fdnum number used to offset the unix domain socket for grouped processes * (such as statistics or peerinfo, which can be shared among others) * * @return new configuration, NULL on error */ struct GNUNET_CONFIGURATION_Handle * GNUNET_TESTING_create_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, uint32_t off, uint16_t * port, uint32_t * upnum, const char *hostname, uint32_t * fdnum) { struct UpdateContext uc; uint16_t orig; char *control_host; char *allowed_hosts; unsigned long long skew_variance; unsigned long long skew_offset; long long actual_offset; orig = *port; uc.nport = *port; uc.upnum = *upnum; uc.fdnum = *fdnum; uc.ret = GNUNET_CONFIGURATION_create (); uc.hostname = hostname; uc.orig = cfg; GNUNET_CONFIGURATION_iterate (cfg, &update_config, &uc); if (uc.nport >= HIGH_PORT) { *port = orig; GNUNET_CONFIGURATION_destroy (uc.ret); return NULL; } if ((GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (cfg, "testing", "skew_variance", &skew_variance)) && (skew_variance > 0)) { skew_offset = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, skew_variance + 1); actual_offset = skew_offset - GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, skew_variance + 1); /* Min is -skew_variance, Max is skew_variance */ skew_offset = skew_variance + actual_offset; /* Normal distribution around 0 */ GNUNET_CONFIGURATION_set_value_number (uc.ret, "testing", "skew_offset", skew_offset); } if (GNUNET_CONFIGURATION_get_value_string (cfg, "testing", "control_host", &control_host) == GNUNET_OK) { if (hostname != NULL) GNUNET_asprintf (&allowed_hosts, "%s; 127.0.0.1; %s;", control_host, hostname); else GNUNET_asprintf (&allowed_hosts, "%s; 127.0.0.1;", control_host); GNUNET_CONFIGURATION_set_value_string (uc.ret, "core", "ACCEPT_FROM", allowed_hosts); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nse", "ACCEPT_FROM", allowed_hosts); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport", "ACCEPT_FROM", allowed_hosts); GNUNET_CONFIGURATION_set_value_string (uc.ret, "dht", "ACCEPT_FROM", allowed_hosts); GNUNET_CONFIGURATION_set_value_string (uc.ret, "statistics", "ACCEPT_FROM", allowed_hosts); GNUNET_CONFIGURATION_set_value_string (uc.ret, "core", "UNIXPATH", ""); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport", "UNIXPATH", ""); GNUNET_CONFIGURATION_set_value_string (uc.ret, "dht", "UNIXPATH", ""); GNUNET_CONFIGURATION_set_value_string (uc.ret, "statistics", "UNIXPATH", ""); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nse", "UNIXPATH", ""); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport-tcp", "USE_LOCALADDR", "YES"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport-udp", "USE_LOCALADDR", "YES"); GNUNET_free_non_null (control_host); GNUNET_free (allowed_hosts); } /* arm needs to know to allow connections from the host on which it is running, * otherwise gnunet-arm is unable to connect to it in some instances */ if (hostname != NULL) { GNUNET_asprintf (&allowed_hosts, "%s; 127.0.0.1;", hostname); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "BINDTO", hostname); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "INTERNAL_ADDRESS", hostname); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "EXTERNAL_ADDRESS", hostname); GNUNET_CONFIGURATION_set_value_string (uc.ret, "disablev6", "BINDTO", "YES"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport-tcp", "USE_LOCALADDR", "YES"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport-udp", "USE_LOCALADDR", "YES"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "arm", "ACCEPT_FROM", allowed_hosts); GNUNET_free (allowed_hosts); } else { GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport-tcp", "USE_LOCALADDR", "YES"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "transport-udp", "USE_LOCALADDR", "YES"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "BINDTO", "127.0.0.1"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "INTERNAL_ADDRESS", "127.0.0.1"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "EXTERNAL_ADDRESS", "127.0.0.1"); GNUNET_CONFIGURATION_set_value_string (uc.ret, "nat", "disablev6", "YES"); } *port = (uint16_t) uc.nport; *upnum = uc.upnum; uc.fdnum++; *fdnum = uc.fdnum; return uc.ret; } /* * Remove entries from the peer connection list * * @param pg the peer group we are working with * @param first index of the first peer * @param second index of the second peer * @param list the peer list to use * @param check UNUSED * * @return the number of connections added (can be 0, 1 or 2) * */ static unsigned int remove_connections (struct GNUNET_TESTING_PeerGroup *pg, unsigned int first, unsigned int second, enum PeerLists list, unsigned int check) { int removed; #if OLD struct PeerConnection **first_list; struct PeerConnection **second_list; struct PeerConnection *first_iter; struct PeerConnection *second_iter; struct PeerConnection **first_tail; struct PeerConnection **second_tail; #else GNUNET_HashCode hash_first; GNUNET_HashCode hash_second; hash_from_uid (first, &hash_first); hash_from_uid (second, &hash_second); #endif removed = 0; #if OLD switch (list) { case ALLOWED: first_list = &pg->peers[first].allowed_peers_head; second_list = &pg->peers[second].allowed_peers_head; first_tail = &pg->peers[first].allowed_peers_tail; second_tail = &pg->peers[second].allowed_peers_tail; break; case CONNECT: first_list = &pg->peers[first].connect_peers_head; second_list = &pg->peers[second].connect_peers_head; first_tail = &pg->peers[first].connect_peers_tail; second_tail = &pg->peers[second].connect_peers_tail; break; case BLACKLIST: first_list = &pg->peers[first].blacklisted_peers_head; second_list = &pg->peers[second].blacklisted_peers_head; first_tail = &pg->peers[first].blacklisted_peers_tail; second_tail = &pg->peers[second].blacklisted_peers_tail; break; case WORKING_SET: first_list = &pg->peers[first].connect_peers_working_set_head; second_list = &pg->peers[second].connect_peers_working_set_head; first_tail = &pg->peers[first].connect_peers_working_set_tail; second_tail = &pg->peers[second].connect_peers_working_set_tail; break; default: GNUNET_break (0); return 0; } first_iter = *first_list; while (first_iter != NULL) { if (first_iter->index == second) { GNUNET_CONTAINER_DLL_remove (*first_list, *first_tail, first_iter); GNUNET_free (first_iter); removed++; break; } first_iter = first_iter->next; } second_iter = *second_list; while (second_iter != NULL) { if (second_iter->index == first) { GNUNET_CONTAINER_DLL_remove (*second_list, *second_tail, second_iter); GNUNET_free (second_iter); removed++; break; } second_iter = second_iter->next; } #else if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (pg-> peers[first].blacklisted_peers, &hash_second)) { GNUNET_CONTAINER_multihashmap_remove_all (pg-> peers[first].blacklisted_peers, &hash_second); } if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (pg-> peers[second].blacklisted_peers, &hash_first)) { GNUNET_CONTAINER_multihashmap_remove_all (pg-> peers[second].blacklisted_peers, &hash_first); } #endif return removed; } /** * Add entries to the some list * * @param pg the peer group we are working with * @param first index of the first peer * @param second index of the second peer * @param list the list type that we should modify * @param check GNUNET_YES to check lists before adding * GNUNET_NO to force add * * @return the number of connections added (can be 0, 1 or 2) * */ static unsigned int add_connections (struct GNUNET_TESTING_PeerGroup *pg, unsigned int first, unsigned int second, enum PeerLists list, unsigned int check) { int added; int add_first; int add_second; struct PeerConnection **first_list; struct PeerConnection **second_list; struct PeerConnection *first_iter; struct PeerConnection *second_iter; struct PeerConnection *new_first; struct PeerConnection *new_second; struct PeerConnection **first_tail; struct PeerConnection **second_tail; switch (list) { case ALLOWED: first_list = &pg->peers[first].allowed_peers_head; second_list = &pg->peers[second].allowed_peers_head; first_tail = &pg->peers[first].allowed_peers_tail; second_tail = &pg->peers[second].allowed_peers_tail; break; case CONNECT: first_list = &pg->peers[first].connect_peers_head; second_list = &pg->peers[second].connect_peers_head; first_tail = &pg->peers[first].connect_peers_tail; second_tail = &pg->peers[second].connect_peers_tail; break; case BLACKLIST: first_list = &pg->peers[first].blacklisted_peers_head; second_list = &pg->peers[second].blacklisted_peers_head; first_tail = &pg->peers[first].blacklisted_peers_tail; second_tail = &pg->peers[second].blacklisted_peers_tail; break; case WORKING_SET: first_list = &pg->peers[first].connect_peers_working_set_head; second_list = &pg->peers[second].connect_peers_working_set_head; first_tail = &pg->peers[first].connect_peers_working_set_tail; second_tail = &pg->peers[second].connect_peers_working_set_tail; break; default: GNUNET_break (0); return 0; } add_first = GNUNET_YES; add_second = GNUNET_YES; if (check == GNUNET_YES) { first_iter = *first_list; while (first_iter != NULL) { if (first_iter->index == second) { add_first = GNUNET_NO; break; } first_iter = first_iter->next; } second_iter = *second_list; while (second_iter != NULL) { if (second_iter->index == first) { add_second = GNUNET_NO; break; } second_iter = second_iter->next; } } added = 0; if (add_first) { new_first = GNUNET_malloc (sizeof (struct PeerConnection)); new_first->index = second; GNUNET_CONTAINER_DLL_insert (*first_list, *first_tail, new_first); pg->peers[first].num_connections++; added++; } if (add_second) { new_second = GNUNET_malloc (sizeof (struct PeerConnection)); new_second->index = first; GNUNET_CONTAINER_DLL_insert (*second_list, *second_tail, new_second); pg->peers[second].num_connections++; added++; } return added; } /** * Scale free network construction as described in: * * "Emergence of Scaling in Random Networks." Science 286, 509-512, 1999. * * Start with a network of "one" peer, then progressively add * peers up to the total number. At each step, iterate over * all possible peers and connect new peer based on number of * existing connections of the target peer. * * @param pg the peer group we are dealing with * @param proc the connection processor to use * @param list the peer list to use * * @return the number of connections created */ static unsigned int create_scale_free (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int total_connections; unsigned int outer_count; unsigned int i; unsigned int previous_total_connections; double random; double probability; GNUNET_assert (pg->total > 1); /* Add a connection between the first two nodes */ total_connections = proc (pg, 0, 1, list, GNUNET_YES); for (outer_count = 1; outer_count < pg->total; outer_count++) { previous_total_connections = total_connections; for (i = 0; i < outer_count; i++) { probability = pg->peers[i].num_connections / (double) previous_total_connections; random = ((double) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX)) / ((double) UINT64_MAX); #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Considering connecting peer %d to peer %d\n", outer_count, i); #endif if (random < probability) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", outer_count, i); #endif total_connections += proc (pg, outer_count, i, list, GNUNET_YES); } } } return total_connections; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. Creates a small world topology * according to the rewired ring construction. The basic * behavior is that a ring topology is created, but with some * probability instead of connecting a peer to the next * neighbor in the ring a connection will be created to a peer * selected uniformly at random. We use the TESTING * PERCENTAGE option to specify what number of * connections each peer should have. Default is 2, * which makes the ring, any given number is multiplied by * the log of the network size; i.e. a PERCENTAGE of 2 makes * each peer have on average 2logn connections. The additional * connections are made at increasing distance around the ring * from the original peer, or to random peers based on the re- * wiring probability. The TESTING * PROBABILITY option is used as the probability that a given * connection is rewired. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_small_world_ring (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int i, j; int nodeToConnect; unsigned int natLog; unsigned int randomPeer; double random, logNModifier, probability; unsigned int smallWorldConnections; int connsPerPeer; char *p_string; int max; int min; unsigned int useAnd; int connect_attempts; logNModifier = 0.5; /* FIXME: default value? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PERCENTAGE", &p_string)) { if (sscanf (p_string, "%lf", &logNModifier) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "LOGNMODIFIER", "TESTING"); GNUNET_free (p_string); } probability = 0.5; /* FIXME: default percentage? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PROBABILITY", &p_string)) { if (sscanf (p_string, "%lf", &probability) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "PERCENTAGE", "TESTING"); GNUNET_free (p_string); } natLog = log (pg->total); connsPerPeer = ceil (natLog * logNModifier); if (connsPerPeer % 2 == 1) connsPerPeer += 1; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Target is %d connections per peer."), connsPerPeer); smallWorldConnections = 0; connect_attempts = 0; for (i = 0; i < pg->total; i++) { useAnd = 0; max = i + connsPerPeer / 2; min = i - connsPerPeer / 2; if (max > pg->total - 1) { max = max - pg->total; useAnd = 1; } if (min < 0) { min = pg->total - 1 + min; useAnd = 1; } for (j = 0; j < connsPerPeer / 2; j++) { random = ((double) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX) / ((double) UINT64_MAX)); if (random < probability) { /* Connect to uniformly selected random peer */ randomPeer = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, pg->total); while ((((randomPeer < max) && (randomPeer > min)) && (useAnd == 0)) || (((randomPeer > min) || (randomPeer < max)) && (useAnd == 1))) { randomPeer = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, pg->total); } smallWorldConnections += proc (pg, i, randomPeer, list, GNUNET_YES); } else { nodeToConnect = i + j + 1; if (nodeToConnect > pg->total - 1) { nodeToConnect = nodeToConnect - pg->total; } connect_attempts += proc (pg, i, nodeToConnect, list, GNUNET_YES); } } } connect_attempts += smallWorldConnections; return connect_attempts; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_nated_internet (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int outer_count, inner_count; unsigned int cutoff; int connect_attempts; double nat_percentage; char *p_string; nat_percentage = 0.6; /* FIXME: default percentage? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PERCENTAGE", &p_string)) { if (sscanf (p_string, "%lf", &nat_percentage) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "PERCENTAGE", "TESTING"); GNUNET_free (p_string); } cutoff = (unsigned int) (nat_percentage * pg->total); connect_attempts = 0; for (outer_count = 0; outer_count < pg->total - 1; outer_count++) { for (inner_count = outer_count + 1; inner_count < pg->total; inner_count++) { if ((outer_count > cutoff) || (inner_count > cutoff)) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", outer_count, inner_count); #endif connect_attempts += proc (pg, outer_count, inner_count, list, GNUNET_YES); } } } return connect_attempts; } #if TOPOLOGY_HACK /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_nated_internet_copy (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int outer_count, inner_count; unsigned int cutoff; int connect_attempts; double nat_percentage; char *p_string; unsigned int count; struct ProgressMeter *conn_meter; nat_percentage = 0.6; /* FIXME: default percentage? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PERCENTAGE", &p_string)) { if (sscanf (p_string, "%lf", &nat_percentage) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "PERCENTAGE", "TESTING"); GNUNET_free (p_string); } cutoff = (unsigned int) (nat_percentage * pg->total); count = 0; for (outer_count = 0; outer_count < pg->total - 1; outer_count++) { for (inner_count = outer_count + 1; inner_count < pg->total; inner_count++) { if ((outer_count > cutoff) || (inner_count > cutoff)) { count++; } } } conn_meter = create_meter (count, "NAT COPY", GNUNET_YES); connect_attempts = 0; for (outer_count = 0; outer_count < pg->total - 1; outer_count++) { for (inner_count = outer_count + 1; inner_count < pg->total; inner_count++) { if ((outer_count > cutoff) || (inner_count > cutoff)) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", outer_count, inner_count); #endif connect_attempts += proc (pg, outer_count, inner_count, list, GNUNET_YES); add_connections (pg, outer_count, inner_count, ALLOWED, GNUNET_NO); update_meter (conn_meter); } } } free_meter (conn_meter); return connect_attempts; } #endif /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_small_world (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int i, j, k; unsigned int square; unsigned int rows; unsigned int cols; unsigned int toggle = 1; unsigned int nodeToConnect; unsigned int natLog; unsigned int node1Row; unsigned int node1Col; unsigned int node2Row; unsigned int node2Col; unsigned int distance; double probability, random, percentage; unsigned int smallWorldConnections; unsigned int small_world_it; char *p_string; int connect_attempts; square = floor (sqrt (pg->total)); rows = square; cols = square; percentage = 0.5; /* FIXME: default percentage? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PERCENTAGE", &p_string)) { if (sscanf (p_string, "%lf", &percentage) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "PERCENTAGE", "TESTING"); GNUNET_free (p_string); } if (percentage < 0.0) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': got %f, needed value greater than 0\n"), "PERCENTAGE", "TESTING", percentage); percentage = 0.5; } probability = 0.5; /* FIXME: default percentage? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PROBABILITY", &p_string)) { if (sscanf (p_string, "%lf", &probability) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "PROBABILITY", "TESTING"); GNUNET_free (p_string); } if (square * square != pg->total) { while (rows * cols < pg->total) { if (toggle % 2 == 0) rows++; else cols++; toggle++; } } #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Connecting nodes in 2d torus topology: %u rows %u columns\n"), rows, cols); #endif connect_attempts = 0; /* Rows and columns are all sorted out, now iterate over all nodes and connect each * to the node to its right and above. Once this is over, we'll have our torus! * Special case for the last node (if the rows and columns are not equal), connect * to the first in the row to maintain topology. */ for (i = 0; i < pg->total; i++) { /* First connect to the node to the right */ if (((i + 1) % cols != 0) && (i + 1 != pg->total)) nodeToConnect = i + 1; else if (i + 1 == pg->total) nodeToConnect = rows * cols - cols; else nodeToConnect = i - cols + 1; connect_attempts += proc (pg, i, nodeToConnect, list, GNUNET_YES); if (i < cols) { nodeToConnect = (rows * cols) - cols + i; if (nodeToConnect >= pg->total) nodeToConnect -= cols; } else nodeToConnect = i - cols; if (nodeToConnect < pg->total) connect_attempts += proc (pg, i, nodeToConnect, list, GNUNET_YES); } natLog = log (pg->total); #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("natural log of %d is %d, will run %d iterations\n"), pg->total, natLog, (int) (natLog * percentage)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Total connections added thus far: %u!\n"), connect_attempts); #endif smallWorldConnections = 0; small_world_it = (unsigned int) (natLog * percentage); if (small_world_it < 1) small_world_it = 1; GNUNET_assert (small_world_it > 0 && small_world_it < (unsigned int) -1); for (i = 0; i < small_world_it; i++) { for (j = 0; j < pg->total; j++) { /* Determine the row and column of node at position j on the 2d torus */ node1Row = j / cols; node1Col = j - (node1Row * cols); for (k = 0; k < pg->total; k++) { /* Determine the row and column of node at position k on the 2d torus */ node2Row = k / cols; node2Col = k - (node2Row * cols); /* Simple Cartesian distance */ distance = abs (node1Row - node2Row) + abs (node1Col - node2Col); if (distance > 1) { /* Calculate probability as 1 over the square of the distance */ probability = 1.0 / (distance * distance); /* Choose a random value between 0 and 1 */ random = ((double) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX)) / ((double) UINT64_MAX); /* If random < probability, then connect the two nodes */ if (random < probability) smallWorldConnections += proc (pg, j, k, list, GNUNET_YES); } } } } connect_attempts += smallWorldConnections; #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Total connections added for small world: %d!\n"), smallWorldConnections); #endif return connect_attempts; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_erdos_renyi (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { double temp_rand; unsigned int outer_count; unsigned int inner_count; int connect_attempts; double probability; char *p_string; probability = 0.5; /* FIXME: default percentage? */ if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "TESTING", "PROBABILITY", &p_string)) { if (sscanf (p_string, "%lf", &probability) != 1) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Invalid value `%s' for option `%s' in section `%s': expected float\n"), p_string, "PROBABILITY", "TESTING"); GNUNET_free (p_string); } connect_attempts = 0; for (outer_count = 0; outer_count < pg->total - 1; outer_count++) { for (inner_count = outer_count + 1; inner_count < pg->total; inner_count++) { temp_rand = ((double) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX)) / ((double) UINT64_MAX); #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("rand is %f probability is %f\n"), temp_rand, probability); #endif if (temp_rand < probability) { connect_attempts += proc (pg, outer_count, inner_count, list, GNUNET_YES); } } } return connect_attempts; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. This particular function creates * the connections for a 2d-torus, plus additional "closest" * connections per peer. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_2d_torus (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int i; unsigned int square; unsigned int rows; unsigned int cols; unsigned int toggle = 1; unsigned int nodeToConnect; int connect_attempts; connect_attempts = 0; square = floor (sqrt (pg->total)); rows = square; cols = square; if (square * square != pg->total) { while (rows * cols < pg->total) { if (toggle % 2 == 0) rows++; else cols++; toggle++; } } #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Connecting nodes in 2d torus topology: %u rows %u columns\n"), rows, cols); #endif /* Rows and columns are all sorted out, now iterate over all nodes and connect each * to the node to its right and above. Once this is over, we'll have our torus! * Special case for the last node (if the rows and columns are not equal), connect * to the first in the row to maintain topology. */ for (i = 0; i < pg->total; i++) { /* First connect to the node to the right */ if (((i + 1) % cols != 0) && (i + 1 != pg->total)) nodeToConnect = i + 1; else if (i + 1 == pg->total) nodeToConnect = rows * cols - cols; else nodeToConnect = i - cols + 1; #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", i, nodeToConnect); #endif connect_attempts += proc (pg, i, nodeToConnect, list, GNUNET_YES); /* Second connect to the node immediately above */ if (i < cols) { nodeToConnect = (rows * cols) - cols + i; if (nodeToConnect >= pg->total) nodeToConnect -= cols; } else nodeToConnect = i - cols; if (nodeToConnect < pg->total) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", i, nodeToConnect); #endif connect_attempts += proc (pg, i, nodeToConnect, list, GNUNET_YES); } } return connect_attempts; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * @param check does the connection processor need to check before * performing an action on the list? * * @return the number of connections that were set up * */ static unsigned int create_clique (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list, unsigned int check) { unsigned int outer_count; unsigned int inner_count; int connect_attempts; struct ProgressMeter *conn_meter; connect_attempts = 0; conn_meter = create_meter ((((pg->total * pg->total) + pg->total) / 2) - pg->total, "Create Clique ", GNUNET_NO); for (outer_count = 0; outer_count < pg->total - 1; outer_count++) { for (inner_count = outer_count + 1; inner_count < pg->total; inner_count++) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", outer_count, inner_count); #endif connect_attempts += proc (pg, outer_count, inner_count, list, check); update_meter (conn_meter); } } reset_meter (conn_meter); free_meter (conn_meter); return connect_attempts; } #if !OLD /** * Iterator over hash map entries. * * @param cls closure the peer group * @param key the key stored in the hashmap is the * index of the peer to connect to * @param value value in the hash map, handle to the peer daemon * @return GNUNET_YES if we should continue to * iterate, * GNUNET_NO if not. */ static int unblacklist_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct UnblacklistContext *un_ctx = cls; uint32_t second_pos; uid_from_hash (key, &second_pos); unblacklist_connections (un_ctx->pg, un_ctx->first_uid, second_pos); return GNUNET_YES; } #endif #if !OLD /** * Create a blacklist topology based on the allowed topology * which disallows any connections not in the allowed topology * at the transport level. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to allow * up connections between two peers * * @return the number of connections that were set up * */ static unsigned int copy_allowed (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc) { unsigned int count; unsigned int total; struct PeerConnection *iter; #if !OLD struct UnblacklistContext un_ctx; un_ctx.pg = pg; #endif total = 0; for (count = 0; count < pg->total - 1; count++) { #if OLD iter = pg->peers[count].allowed_peers_head; while (iter != NULL) { remove_connections (pg, count, iter->index, BLACKLIST, GNUNET_YES); //unblacklist_connections(pg, count, iter->index); iter = iter->next; } #else un_ctx.first_uid = count; total += GNUNET_CONTAINER_multihashmap_iterate (pg->peers[count].allowed_peers, &unblacklist_iterator, &un_ctx); #endif } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Unblacklisted %u peers\n", total); return total; } #endif /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list which list should be modified * * @return the number of connections that were set up * */ static unsigned int create_line (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int count; unsigned int connect_attempts; connect_attempts = 0; /* Connect each peer to the next highest numbered peer */ for (count = 0; count < pg->total - 1; count++) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", count, count + 1); #endif connect_attempts += proc (pg, count, count + 1, list, GNUNET_YES); } return connect_attempts; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param filename the file to read topology information from * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_from_file (struct GNUNET_TESTING_PeerGroup *pg, char *filename, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { int connect_attempts; unsigned int first_peer_index; unsigned int second_peer_index; struct stat frstat; int count; char *data; const char *buf; unsigned int total_peers; enum States curr_state; connect_attempts = 0; if (GNUNET_OK != GNUNET_DISK_file_test (filename)) GNUNET_DISK_fn_write (filename, NULL, 0, GNUNET_DISK_PERM_USER_READ); if ((0 != STAT (filename, &frstat)) || (frstat.st_size == 0)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not open file `%s' specified for topology!", filename); return connect_attempts; } data = GNUNET_malloc_large (frstat.st_size); GNUNET_assert (data != NULL); if (frstat.st_size != GNUNET_DISK_fn_read (filename, data, frstat.st_size)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not read file %s specified for host list, ending test!", filename); GNUNET_free (data); return connect_attempts; } buf = data; count = 0; first_peer_index = 0; /* First line should contain a single integer, specifying the number of peers */ /* Each subsequent line should contain this format PEER_INDEX:OTHER_PEER_INDEX[,...] */ curr_state = NUM_PEERS; while (count < frstat.st_size - 1) { if ((buf[count] == '\n') || (buf[count] == ' ')) { count++; continue; } switch (curr_state) { case NUM_PEERS: errno = 0; total_peers = strtoul (&buf[count], NULL, 10); if (errno != 0) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to read number of peers from topology file!\n"); GNUNET_free (data); return connect_attempts; } #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found %u total peers in topology\n", total_peers); #endif GNUNET_assert (total_peers == pg->total); curr_state = PEER_INDEX; while ((buf[count] != '\n') && (count < frstat.st_size - 1)) count++; count++; break; case PEER_INDEX: errno = 0; first_peer_index = strtoul (&buf[count], NULL, 10); if (errno != 0) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to read peer index from topology file!\n"); GNUNET_free (data); return connect_attempts; } while ((buf[count] != ':') && (count < frstat.st_size - 1)) count++; count++; curr_state = OTHER_PEER_INDEX; break; case COLON: if (1 == sscanf (&buf[count], ":")) curr_state = OTHER_PEER_INDEX; count++; break; case OTHER_PEER_INDEX: errno = 0; second_peer_index = strtoul (&buf[count], NULL, 10); if (errno != 0) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to peer index from topology file!\n"); GNUNET_free (data); return connect_attempts; } /* Assume file is written with first peer 1, but array index is 0 */ connect_attempts += proc (pg, first_peer_index - 1, second_peer_index - 1, list, GNUNET_YES); while ((buf[count] != '\n') && (buf[count] != ',') && (count < frstat.st_size - 1)) count++; if (buf[count] == '\n') { curr_state = PEER_INDEX; } else if (buf[count] != ',') { curr_state = OTHER_PEER_INDEX; } count++; break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Found bad data in topology file while in state %d!\n", curr_state); GNUNET_break (0); GNUNET_free (data); return connect_attempts; } } GNUNET_free (data); return connect_attempts; } /** * Create a topology given a peer group (set of running peers) * and a connection processor. * * @param pg the peergroup to create the topology on * @param proc the connection processor to call to actually set * up connections between two peers * @param list the peer list to use * * @return the number of connections that were set up * */ static unsigned int create_ring (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { unsigned int count; int connect_attempts; connect_attempts = 0; /* Connect each peer to the next highest numbered peer */ for (count = 0; count < pg->total - 1; count++) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting peer %d to peer %d\n", count, count + 1); #endif connect_attempts += proc (pg, count, count + 1, list, GNUNET_YES); } /* Connect the last peer to the first peer */ connect_attempts += proc (pg, pg->total - 1, 0, list, GNUNET_YES); return connect_attempts; } #if !OLD /** * Iterator for writing friends of a peer to a file. * * @param cls closure, an open writable file handle * @param key the key the daemon was stored under * @param value the GNUNET_TESTING_Daemon that needs to be written. * * @return GNUNET_YES to continue iteration * * TODO: Could replace friend_file_iterator and blacklist_file_iterator * with a single file_iterator that takes a closure which contains * the prefix to write before the peer. Then this could be used * for blacklisting multiple transports and writing the friend * file. I'm sure *someone* will complain loudly about other * things that negate these functions even existing so no point in * "fixing" now. */ static int friend_file_iterator (void *cls, const GNUNET_HashCode * key, void *value) { FILE *temp_friend_handle = cls; struct GNUNET_TESTING_Daemon *peer = value; struct GNUNET_PeerIdentity *temppeer; struct GNUNET_CRYPTO_HashAsciiEncoded peer_enc; temppeer = &peer->id; GNUNET_CRYPTO_hash_to_enc (&temppeer->hashPubKey, &peer_enc); FPRINTF (temp_friend_handle, "%s\n", (char *) &peer_enc); return GNUNET_YES; } struct BlacklistContext { /* * The (open) file handle to write to */ FILE *temp_file_handle; /* * The transport that this peer will be blacklisted on. */ char *transport; }; /** * Iterator for writing blacklist data to appropriate files. * * @param cls closure, an open writable file handle * @param key the key the daemon was stored under * @param value the GNUNET_TESTING_Daemon that needs to be written. * * @return GNUNET_YES to continue iteration */ static int blacklist_file_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct BlacklistContext *blacklist_ctx = cls; struct GNUNET_TESTING_Daemon *peer = value; struct GNUNET_PeerIdentity *temppeer; struct GNUNET_CRYPTO_HashAsciiEncoded peer_enc; temppeer = &peer->id; GNUNET_CRYPTO_hash_to_enc (&temppeer->hashPubKey, &peer_enc); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Writing entry %s:%s to file\n", blacklist_ctx->transport, (char *) &peer_enc); FPRINTF (blacklist_ctx->temp_file_handle, "%s:%s\n", blacklist_ctx->transport, (char *) &peer_enc); return GNUNET_YES; } #endif /* * Create the friend files based on the PeerConnection's * of each peer in the peer group, and copy the files * to the appropriate place * * @param pg the peer group we are dealing with */ static int create_and_copy_friend_files (struct GNUNET_TESTING_PeerGroup *pg) { FILE *temp_friend_handle; unsigned int pg_iter; char *temp_service_path; struct GNUNET_OS_Process **procarr; char *arg; char *mytemp; #if NOT_STUPID enum GNUNET_OS_ProcessStatusType type; unsigned long return_code; int count; int max_wait = 10; #endif int ret; ret = GNUNET_OK; #if OLD struct GNUNET_CRYPTO_HashAsciiEncoded peer_enc; struct PeerConnection *conn_iter; #endif procarr = GNUNET_malloc (sizeof (struct GNUNET_OS_Process *) * pg->total); for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { mytemp = GNUNET_DISK_mktemp ("friends"); GNUNET_assert (mytemp != NULL); temp_friend_handle = FOPEN (mytemp, "wt"); GNUNET_assert (temp_friend_handle != NULL); #if OLD conn_iter = pg->peers[pg_iter].allowed_peers_head; while (conn_iter != NULL) { GNUNET_CRYPTO_hash_to_enc (&pg->peers[conn_iter->index].daemon-> id.hashPubKey, &peer_enc); FPRINTF (temp_friend_handle, "%s\n", (char *) &peer_enc); conn_iter = conn_iter->next; } #else GNUNET_CONTAINER_multihashmap_iterate (pg->peers[pg_iter].allowed_peers, &friend_file_iterator, temp_friend_handle); #endif FCLOSE (temp_friend_handle); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (pg->peers[pg_iter].daemon->cfg, "PATHS", "SERVICEHOME", &temp_service_path)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("No `%s' specified in peer configuration in section `%s', cannot copy friends file!\n"), "SERVICEHOME", "PATHS"); if (UNLINK (mytemp) != 0) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", mytemp); GNUNET_free (mytemp); break; } if (pg->peers[pg_iter].daemon->hostname == NULL) /* Local, just copy the file */ { GNUNET_asprintf (&arg, "%s/friends", temp_service_path); #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Copying file with RENAME(%s,%s)\n", mytemp, arg); #endif RENAME (mytemp, arg); procarr[pg_iter] = NULL; GNUNET_free (arg); } else /* Remote, scp the file to the correct place */ { if (NULL != pg->peers[pg_iter].daemon->username) GNUNET_asprintf (&arg, "%s@%s:%s/friends", pg->peers[pg_iter].daemon->username, pg->peers[pg_iter].daemon->hostname, temp_service_path); else GNUNET_asprintf (&arg, "%s:%s/friends", pg->peers[pg_iter].daemon->hostname, temp_service_path); procarr[pg_iter] = GNUNET_OS_start_process (GNUNET_NO, NULL, NULL, "scp", "scp", mytemp, arg, NULL); GNUNET_assert (procarr[pg_iter] != NULL); ret = GNUNET_OS_process_wait (procarr[pg_iter]); /* FIXME: schedule this, throttle! */ GNUNET_OS_process_close (procarr[pg_iter]); if (ret != GNUNET_OK) { /* FIXME: free contents of 'procarr' array */ GNUNET_free (procarr); GNUNET_free (temp_service_path); GNUNET_free (mytemp); GNUNET_free (arg); return ret; } procarr[pg_iter] = NULL; #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Copying file with command scp %s %s\n", mytemp, arg); #endif GNUNET_free (arg); } GNUNET_free (temp_service_path); GNUNET_free (mytemp); } #if NOT_STUPID count = 0; ret = GNUNET_SYSERR; while ((count < max_wait) && (ret != GNUNET_OK)) { ret = GNUNET_OK; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Checking copy status of file %d\n", pg_iter); #endif if (procarr[pg_iter] != NULL) /* Check for already completed! */ { if (GNUNET_OS_process_status (procarr[pg_iter], &type, &return_code) != GNUNET_OK) { ret = GNUNET_SYSERR; } else if ((type != GNUNET_OS_PROCESS_EXITED) || (return_code != 0)) { ret = GNUNET_SYSERR; } else { GNUNET_OS_process_close (procarr[pg_iter]); procarr[pg_iter] = NULL; #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "File %d copied\n", pg_iter); #endif } } } count++; if (ret == GNUNET_SYSERR) { /* FIXME: why sleep here? -CG */ sleep (1); } } #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Finished copying all friend files!\n")); #endif #endif GNUNET_free (procarr); return ret; } /* * Create the blacklist files based on the PeerConnection's * of each peer in the peer group, and copy the files * to the appropriate place. * * @param pg the peer group we are dealing with * @param transports space delimited list of transports to blacklist */ static int create_and_copy_blacklist_files (struct GNUNET_TESTING_PeerGroup *pg, const char *transports) { FILE *temp_file_handle; unsigned int pg_iter; char *temp_service_path; struct GNUNET_OS_Process **procarr; char *arg; char *mytemp; enum GNUNET_OS_ProcessStatusType type; unsigned long return_code; int count; int ret; int max_wait = 10; int transport_len; unsigned int i; char *pos; char *temp_transports; #if OLD struct GNUNET_CRYPTO_HashAsciiEncoded peer_enc; struct PeerConnection *conn_iter; #else static struct BlacklistContext blacklist_ctx; #endif procarr = GNUNET_malloc (sizeof (struct GNUNET_OS_Process *) * pg->total); for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { mytemp = GNUNET_DISK_mktemp ("blacklist"); GNUNET_assert (mytemp != NULL); temp_file_handle = FOPEN (mytemp, "wt"); GNUNET_assert (temp_file_handle != NULL); temp_transports = GNUNET_strdup (transports); #if !OLD blacklist_ctx.temp_file_handle = temp_file_handle; #endif transport_len = strlen (temp_transports) + 1; pos = NULL; for (i = 0; i < transport_len; i++) { if ((temp_transports[i] == ' ') && (pos == NULL)) continue; /* At start of string (whitespace) */ else if ((temp_transports[i] == ' ') || (temp_transports[i] == '\0')) /* At end of string */ { temp_transports[i] = '\0'; #if OLD conn_iter = pg->peers[pg_iter].blacklisted_peers_head; while (conn_iter != NULL) { GNUNET_CRYPTO_hash_to_enc (&pg->peers[conn_iter->index].daemon-> id.hashPubKey, &peer_enc); FPRINTF (temp_file_handle, "%s:%s\n", pos, (char *) &peer_enc); conn_iter = conn_iter->next; } #else blacklist_ctx.transport = pos; (void) GNUNET_CONTAINER_multihashmap_iterate (pg-> peers [pg_iter].blacklisted_peers, &blacklist_file_iterator, &blacklist_ctx); #endif pos = NULL; } /* At beginning of actual string */ else if (pos == NULL) { pos = &temp_transports[i]; } } GNUNET_free (temp_transports); FCLOSE (temp_file_handle); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (pg->peers[pg_iter].daemon->cfg, "PATHS", "SERVICEHOME", &temp_service_path)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("No `%s' specified in peer configuration in section `%s', cannot copy friends file!\n"), "SERVICEHOME", "PATHS"); if (UNLINK (mytemp) != 0) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", mytemp); GNUNET_free (mytemp); break; } if (pg->peers[pg_iter].daemon->hostname == NULL) /* Local, just copy the file */ { GNUNET_asprintf (&arg, "%s/blacklist", temp_service_path); RENAME (mytemp, arg); procarr[pg_iter] = NULL; #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Copying file with RENAME (%s,%s)\n"), mytemp, arg); #endif GNUNET_free (arg); } else /* Remote, scp the file to the correct place */ { if (NULL != pg->peers[pg_iter].daemon->username) GNUNET_asprintf (&arg, "%s@%s:%s/blacklist", pg->peers[pg_iter].daemon->username, pg->peers[pg_iter].daemon->hostname, temp_service_path); else GNUNET_asprintf (&arg, "%s:%s/blacklist", pg->peers[pg_iter].daemon->hostname, temp_service_path); procarr[pg_iter] = GNUNET_OS_start_process (GNUNET_NO, NULL, NULL, "scp", "scp", mytemp, arg, NULL); GNUNET_assert (procarr[pg_iter] != NULL); GNUNET_OS_process_wait (procarr[pg_iter]); /* FIXME: add scheduled blacklist file copy that parallelizes file copying! */ #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Copying file with command scp %s %s\n"), mytemp, arg); #endif GNUNET_free (arg); } GNUNET_free (temp_service_path); GNUNET_free (mytemp); } count = 0; ret = GNUNET_SYSERR; while ((count < max_wait) && (ret != GNUNET_OK)) { ret = GNUNET_OK; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Checking copy status of file %d\n"), pg_iter); #endif if (procarr[pg_iter] != NULL) /* Check for already completed! */ { if (GNUNET_OS_process_status (procarr[pg_iter], &type, &return_code) != GNUNET_OK) { ret = GNUNET_SYSERR; } else if ((type != GNUNET_OS_PROCESS_EXITED) || (return_code != 0)) { ret = GNUNET_SYSERR; } else { GNUNET_OS_process_close (procarr[pg_iter]); procarr[pg_iter] = NULL; #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("File %d copied\n"), pg_iter); #endif } } } count++; if (ret == GNUNET_SYSERR) { /* FIXME: why sleep here? -CG */ sleep (1); } } #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Finished copying all blacklist files!\n")); #endif GNUNET_free (procarr); return ret; } /* Forward Declaration */ static void schedule_connect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); /** * Choose a random peer's next connection to create, and * call schedule_connect to set up the connect task. * * @param pg the peer group to connect */ static void preschedule_connect (struct GNUNET_TESTING_PeerGroup *pg) { struct ConnectTopologyContext *ct_ctx = &pg->ct_ctx; struct PeerConnection *connection_iter; struct ConnectContext *connect_context; uint32_t random_peer; if (ct_ctx->remaining_connections == 0) return; random_peer = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, pg->total); while (pg->peers[random_peer].connect_peers_head == NULL) random_peer = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, pg->total); connection_iter = pg->peers[random_peer].connect_peers_head; connect_context = GNUNET_malloc (sizeof (struct ConnectContext)); connect_context->first_index = random_peer; connect_context->second_index = connection_iter->index; connect_context->ct_ctx = ct_ctx; connect_context->task = GNUNET_SCHEDULER_add_now (&schedule_connect, connect_context); GNUNET_CONTAINER_DLL_insert (pg->cc_head, pg->cc_tail, connect_context); GNUNET_CONTAINER_DLL_remove (pg->peers[random_peer].connect_peers_head, pg->peers[random_peer].connect_peers_tail, connection_iter); GNUNET_free (connection_iter); ct_ctx->remaining_connections--; } #if USE_SEND_HELLOS /* Forward declaration */ static void schedule_send_hellos (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); /** * Close connections and free the hello context. * * @param cls the 'struct SendHelloContext *' * @param tc scheduler context */ static void free_hello_context (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct SendHelloContext *send_hello_context = cls; if (send_hello_context->peer->daemon->server != NULL) { GNUNET_CORE_disconnect (send_hello_context->peer->daemon->server); send_hello_context->peer->daemon->server = NULL; } if (send_hello_context->peer->daemon->th != NULL) { GNUNET_TRANSPORT_disconnect (send_hello_context->peer->daemon->th); send_hello_context->peer->daemon->th = NULL; } if (send_hello_context->core_connect_task != GNUNET_SCHEDULER_NO_TASK) { GNUNET_SCHEDULER_cancel (send_hello_context->core_connect_task); send_hello_context->core_connect_task = GNUNET_SCHEDULER_NO_TASK; } send_hello_context->pg->outstanding_connects--; GNUNET_free (send_hello_context); } /** * For peers that haven't yet connected, notify * the caller that they have failed (timeout). * * @param cls the 'struct SendHelloContext *' * @param tc scheduler context */ static void notify_remaining_connections_failed (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct SendHelloContext *send_hello_context = cls; struct GNUNET_TESTING_PeerGroup *pg = send_hello_context->pg; struct PeerConnection *connection; GNUNET_CORE_disconnect (send_hello_context->peer->daemon->server); send_hello_context->peer->daemon->server = NULL; connection = send_hello_context->peer->connect_peers_head; while (connection != NULL) { if (pg->notify_connection != NULL) { pg->notify_connection (pg->notify_connection_cls, &send_hello_context->peer->daemon->id, &pg->peers[connection->index].daemon->id, 0, /* FIXME */ send_hello_context->peer->daemon->cfg, pg->peers[connection->index].daemon->cfg, send_hello_context->peer->daemon, pg->peers[connection->index].daemon, "Peers failed to connect (timeout)"); } GNUNET_CONTAINER_DLL_remove (send_hello_context->peer->connect_peers_head, send_hello_context->peer->connect_peers_tail, connection); GNUNET_free (connection); connection = connection->next; } GNUNET_SCHEDULER_add_now (&free_hello_context, send_hello_context); #if BAD other_peer = &pg->peers[connection->index]; #endif } /** * For peers that haven't yet connected, send * CORE connect requests. * * @param cls the 'struct SendHelloContext *' * @param tc scheduler context */ static void send_core_connect_requests (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct SendHelloContext *send_hello_context = cls; struct PeerConnection *conn; GNUNET_assert (send_hello_context->peer->daemon->server != NULL); send_hello_context->core_connect_task = GNUNET_SCHEDULER_NO_TASK; send_hello_context->connect_attempts++; if (send_hello_context->connect_attempts < send_hello_context->pg->ct_ctx.connect_attempts) { conn = send_hello_context->peer->connect_peers_head; while (conn != NULL) { GNUNET_TRANSPORT_try_connect (send_hello_context->peer->daemon->th, &send_hello_context->pg->peers[conn-> index].daemon-> id); conn = conn->next; } send_hello_context->core_connect_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_divide (send_hello_context->pg-> ct_ctx.connect_timeout, send_hello_context->pg-> ct_ctx.connect_attempts), &send_core_connect_requests, send_hello_context); } else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Timeout before all connections created, marking rest as failed!\n"); GNUNET_SCHEDULER_add_now (¬ify_remaining_connections_failed, send_hello_context); } } /** * Success, connection is up. Signal client our success. * * @param cls our "struct SendHelloContext" * @param peer identity of the peer that has connected * @param atsi performance information * * FIXME: remove peers from BOTH lists, call notify twice, should * double the speed of connections as long as the list iteration * doesn't take too long! */ static void core_connect_notify (void *cls, const struct GNUNET_PeerIdentity *peer, const struct GNUNET_ATS_Information *atsi) { struct SendHelloContext *send_hello_context = cls; struct PeerConnection *connection; struct GNUNET_TESTING_PeerGroup *pg = send_hello_context->pg; #if BAD struct PeerData *other_peer; #endif #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected peer %s to peer %s\n", ctx->d1->shortname, GNUNET_i2s (peer)); #endif if (0 == memcmp (&send_hello_context->peer->daemon->id, peer, sizeof (struct GNUNET_PeerIdentity))) return; connection = send_hello_context->peer->connect_peers_head; #if BAD other_peer = NULL; #endif while ((connection != NULL) && (0 != memcmp (&pg->peers[connection->index].daemon->id, peer, sizeof (struct GNUNET_PeerIdentity)))) { connection = connection->next; } if (connection == NULL) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected peer %s to %s, not in list (no problem(?))\n", GNUNET_i2s (peer), send_hello_context->peer->daemon->shortname); } else { #if BAD other_peer = &pg->peers[connection->index]; #endif if (pg->notify_connection != NULL) { pg->notify_connection (pg->notify_connection_cls, &send_hello_context->peer->daemon->id, peer, 0, /* FIXME */ send_hello_context->peer->daemon->cfg, pg->peers[connection->index].daemon->cfg, send_hello_context->peer->daemon, pg->peers[connection->index].daemon, NULL); } GNUNET_CONTAINER_DLL_remove (send_hello_context->peer->connect_peers_head, send_hello_context->peer->connect_peers_tail, connection); GNUNET_free (connection); } #if BAD /* Notify of reverse connection and remove from other peers list of outstanding */ if (other_peer != NULL) { connection = other_peer->connect_peers_head; while ((connection != NULL) && (0 != memcmp (&send_hello_context->peer->daemon->id, &pg->peers[connection->index].daemon->id, sizeof (struct GNUNET_PeerIdentity)))) { connection = connection->next; } if (connection != NULL) { if (pg->notify_connection != NULL) { pg->notify_connection (pg->notify_connection_cls, peer, &send_hello_context->peer->daemon->id, 0, /* FIXME */ pg->peers[connection->index].daemon->cfg, send_hello_context->peer->daemon->cfg, pg->peers[connection->index].daemon, send_hello_context->peer->daemon, NULL); } GNUNET_CONTAINER_DLL_remove (other_peer->connect_peers_head, other_peer->connect_peers_tail, connection); GNUNET_free (connection); } } #endif if (send_hello_context->peer->connect_peers_head == NULL) { GNUNET_SCHEDULER_add_now (&free_hello_context, send_hello_context); } } /** * Notify of a successful connection to the core service. * * @param cls a struct SendHelloContext * * @param server handle to the core service * @param my_identity the peer identity of this peer */ void core_init (void *cls, struct GNUNET_CORE_Handle *server, struct GNUNET_PeerIdentity *my_identity) { struct SendHelloContext *send_hello_context = cls; send_hello_context->core_ready = GNUNET_YES; } /** * Function called once a hello has been sent * to the transport, move on to the next one * or go away forever. * * @param cls the 'struct SendHelloContext *' * @param tc scheduler context */ static void hello_sent_callback (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct SendHelloContext *send_hello_context = cls; //unsigned int pg_iter; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) { GNUNET_free (send_hello_context); return; } send_hello_context->pg->remaining_hellos--; #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sent HELLO, have %d remaining!\n", send_hello_context->pg->remaining_hellos); #endif if (send_hello_context->peer_pos == NULL) /* All HELLOs (for this peer!) have been transmitted! */ { #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "All hellos for this peer sent, disconnecting transport!\n"); #endif GNUNET_assert (send_hello_context->peer->daemon->th != NULL); GNUNET_TRANSPORT_disconnect (send_hello_context->peer->daemon->th); send_hello_context->peer->daemon->th = NULL; GNUNET_assert (send_hello_context->peer->daemon->server == NULL); send_hello_context->peer->daemon->server = GNUNET_CORE_connect (send_hello_context->peer->cfg, 1, send_hello_context, &core_init, &core_connect_notify, NULL, NULL, NULL, GNUNET_NO, NULL, GNUNET_NO, no_handlers); send_hello_context->core_connect_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_divide (send_hello_context->pg-> ct_ctx.connect_timeout, send_hello_context->pg-> ct_ctx.connect_attempts), &send_core_connect_requests, send_hello_context); } else GNUNET_SCHEDULER_add_now (&schedule_send_hellos, send_hello_context); } /** * Connect to a peer, give it all the HELLO's of those peers * we will later ask it to connect to. * * @param ct_ctx the overall connection context */ static void schedule_send_hellos (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct SendHelloContext *send_hello_context = cls; struct GNUNET_TESTING_PeerGroup *pg = send_hello_context->pg; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) { GNUNET_free (send_hello_context); return; } GNUNET_assert (send_hello_context->peer_pos != NULL); /* All of the HELLO sends to be scheduled have been scheduled! */ if (((send_hello_context->peer->daemon->th == NULL) && (pg->outstanding_connects > pg->max_outstanding_connections)) || (pg->stop_connects == GNUNET_YES)) { #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Delaying connect, we have too many outstanding connections!\n")); #endif GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_send_hellos, send_hello_context); } else { #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating connection, outstanding_connections is %d\n"), outstanding_connects); #endif if (send_hello_context->peer->daemon->th == NULL) { pg->outstanding_connects++; /* Actual TRANSPORT, CORE connections! */ send_hello_context->peer->daemon->th = GNUNET_TRANSPORT_connect (send_hello_context->peer->cfg, NULL, send_hello_context, NULL, NULL, NULL); } #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Offering HELLO of peer %s to peer %s\n"), send_hello_context->peer->daemon->shortname, pg->peers[send_hello_context->peer_pos->index]. daemon->shortname); #endif GNUNET_TRANSPORT_offer_hello (send_hello_context->peer->daemon->th, (const struct GNUNET_MessageHeader *) pg->peers[send_hello_context->peer_pos-> index].daemon->hello, &hello_sent_callback, send_hello_context); send_hello_context->peer_pos = send_hello_context->peer_pos->next; GNUNET_assert (send_hello_context->peer->daemon->th != NULL); } } #endif /** * Internal notification of a connection, kept so that we can ensure some connections * happen instead of flooding all testing daemons with requests to connect. */ static void internal_connect_notify (void *cls, const struct GNUNET_PeerIdentity *first, const struct GNUNET_PeerIdentity *second, uint32_t distance, const struct GNUNET_CONFIGURATION_Handle *first_cfg, const struct GNUNET_CONFIGURATION_Handle *second_cfg, struct GNUNET_TESTING_Daemon *first_daemon, struct GNUNET_TESTING_Daemon *second_daemon, const char *emsg) { struct ConnectContext *connect_ctx = cls; struct ConnectTopologyContext *ct_ctx = connect_ctx->ct_ctx; struct GNUNET_TESTING_PeerGroup *pg = ct_ctx->pg; struct PeerConnection *connection; GNUNET_assert (NULL != connect_ctx->cc); connect_ctx->cc = NULL; GNUNET_assert (0 < pg->outstanding_connects); pg->outstanding_connects--; GNUNET_CONTAINER_DLL_remove (pg->cc_head, pg->cc_tail, connect_ctx); /* * Check whether the inverse connection has been scheduled yet, * if not, we can remove it from the other peers list and avoid * even trying to connect them again! */ connection = pg->peers[connect_ctx->second_index].connect_peers_head; #if BAD other_peer = NULL; #endif while ((connection != NULL) && (0 != memcmp (first, &pg->peers[connection->index].daemon->id, sizeof (struct GNUNET_PeerIdentity)))) connection = connection->next; if (connection != NULL) /* Can safely remove! */ { GNUNET_assert (0 < ct_ctx->remaining_connections); ct_ctx->remaining_connections--; if (pg->notify_connection != NULL) /* Notify of reverse connection */ pg->notify_connection (pg->notify_connection_cls, second, first, distance, second_cfg, first_cfg, second_daemon, first_daemon, emsg); GNUNET_CONTAINER_DLL_remove (pg-> peers[connect_ctx-> second_index].connect_peers_head, pg->peers[connect_ctx-> second_index].connect_peers_tail, connection); GNUNET_free (connection); } if (ct_ctx->remaining_connections == 0) { if (ct_ctx->notify_connections_done != NULL) { ct_ctx->notify_connections_done (ct_ctx->notify_cls, NULL); ct_ctx->notify_connections_done = NULL; } } else preschedule_connect (pg); if (pg->notify_connection != NULL) pg->notify_connection (pg->notify_connection_cls, first, second, distance, first_cfg, second_cfg, first_daemon, second_daemon, emsg); GNUNET_free (connect_ctx); } /** * Either delay a connection (because there are too many outstanding) * or schedule it for right now. * * @param cls a connection context * @param tc the task runtime context */ static void schedule_connect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct ConnectContext *connect_context = cls; struct GNUNET_TESTING_PeerGroup *pg = connect_context->ct_ctx->pg; connect_context->task = GNUNET_SCHEDULER_NO_TASK; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) return; if ((pg->outstanding_connects > pg->max_outstanding_connections) || (pg->stop_connects == GNUNET_YES)) { #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Delaying connect, we have too many outstanding connections!\n")); #endif connect_context->task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_connect, connect_context); return; } #if VERBOSE_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Creating connection, outstanding_connections is %d (max %d)\n"), pg->outstanding_connects, pg->max_outstanding_connections); #endif pg->outstanding_connects++; pg->total_connects_scheduled++; GNUNET_assert (NULL == connect_context->cc); connect_context->cc = GNUNET_TESTING_daemons_connect (pg-> peers[connect_context-> first_index].daemon, pg->peers[connect_context-> second_index].daemon, connect_context->ct_ctx->connect_timeout, connect_context->ct_ctx->connect_attempts, #if USE_SEND_HELLOS GNUNET_NO, #else GNUNET_YES, #endif &internal_connect_notify, connect_context); } #if !OLD /** * Iterator for actually scheduling connections to be created * between two peers. * * @param cls closure, a GNUNET_TESTING_Daemon * @param key the key the second Daemon was stored under * @param value the GNUNET_TESTING_Daemon that the first is to connect to * * @return GNUNET_YES to continue iteration */ static int connect_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct ConnectTopologyContext *ct_ctx = cls; struct PeerData *first = ct_ctx->first; struct GNUNET_TESTING_Daemon *second = value; struct ConnectContext *connect_context; connect_context = GNUNET_malloc (sizeof (struct ConnectContext)); connect_context->first = first->daemon; connect_context->second = second; connect_context->ct_ctx = ct_ctx; connect_context->task = GNUNET_SCHEDULER_add_now (&schedule_connect, connect_context); GNUNET_CONTAINER_DLL_insert (ct_ctx->pg->cc_head, ct_ctx->pg->cc_tail, connect_context); return GNUNET_YES; } #endif #if !OLD /** * Iterator for copying all entries in the allowed hashmap to the * connect hashmap. * * @param cls closure, a GNUNET_TESTING_Daemon * @param key the key the second Daemon was stored under * @param value the GNUNET_TESTING_Daemon that the first is to connect to * * @return GNUNET_YES to continue iteration */ static int copy_topology_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct PeerData *first = cls; GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (first->connect_peers, key, value, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); return GNUNET_YES; } #endif /** * Make the peers to connect the same as those that are allowed to be * connected. * * @param pg the peer group */ static int copy_allowed_topology (struct GNUNET_TESTING_PeerGroup *pg) { unsigned int pg_iter; int ret; int total; #if OLD struct PeerConnection *iter; #endif total = 0; ret = 0; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if OLD iter = pg->peers[pg_iter].allowed_peers_head; while (iter != NULL) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Creating connection between %d and %d\n", pg_iter, iter->index); total += add_connections (pg, pg_iter, iter->index, CONNECT, GNUNET_YES); //total += add_actual_connections(pg, pg_iter, iter->index); iter = iter->next; } #else ret = GNUNET_CONTAINER_multihashmap_iterate (pg->peers[pg_iter].allowed_peers, ©_topology_iterator, &pg->peers[pg_iter]); #endif if (GNUNET_SYSERR == ret) return GNUNET_SYSERR; total = total + ret; } return total; } /** * Connect the topology as specified by the PeerConnection's * of each peer in the peer group * * @param pg the peer group we are dealing with * @param connect_timeout how long try connecting two peers * @param connect_attempts how many times (max) to attempt * @param notify_callback callback to notify when finished * @param notify_cls closure for notify callback * * @return the number of connections that will be attempted */ static int connect_topology (struct GNUNET_TESTING_PeerGroup *pg, struct GNUNET_TIME_Relative connect_timeout, unsigned int connect_attempts, GNUNET_TESTING_NotifyCompletion notify_callback, void *notify_cls) { unsigned int pg_iter; unsigned int total; #if OLD struct PeerConnection *connection_iter; #endif #if USE_SEND_HELLOS struct SendHelloContext *send_hello_context; #endif total = 0; pg->ct_ctx.notify_connections_done = notify_callback; pg->ct_ctx.notify_cls = notify_cls; pg->ct_ctx.pg = pg; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if OLD connection_iter = pg->peers[pg_iter].connect_peers_head; while (connection_iter != NULL) { connection_iter = connection_iter->next; total++; } #else total += GNUNET_CONTAINER_multihashmap_size (pg->peers[pg_iter].connect_peers); #endif } if (total == 0) return total; pg->ct_ctx.connect_timeout = connect_timeout; pg->ct_ctx.connect_attempts = connect_attempts; pg->ct_ctx.remaining_connections = total; #if USE_SEND_HELLOS /* First give all peers the HELLO's of other peers (connect to first peer's transport service, give HELLO's of other peers, continue...) */ pg->remaining_hellos = total; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { send_hello_context = GNUNET_malloc (sizeof (struct SendHelloContext)); send_hello_context->peer = &pg->peers[pg_iter]; send_hello_context->peer_pos = pg->peers[pg_iter].connect_peers_head; send_hello_context->pg = pg; GNUNET_SCHEDULER_add_now (&schedule_send_hellos, send_hello_context); } #else for (pg_iter = 0; pg_iter < pg->max_outstanding_connections; pg_iter++) { preschedule_connect (pg); } #endif return total; } /** * Takes a peer group and creates a topology based on the * one specified. Creates a topology means generates friend * files for the peers so they can only connect to those allowed * by the topology. This will only have an effect once peers * are started if the FRIENDS_ONLY option is set in the base * config. Also takes an optional restrict topology which * disallows connections based on particular transports * UNLESS they are specified in the restricted topology. * * @param pg the peer group struct representing the running peers * @param topology which topology to connect the peers in * @param restrict_topology disallow restrict_transports transport * connections to peers NOT in this topology * use GNUNET_TESTING_TOPOLOGY_NONE for no restrictions * @param restrict_transports space delimited list of transports to blacklist * to create restricted topology * * @return the maximum number of connections were all allowed peers * connected to each other */ unsigned int GNUNET_TESTING_create_topology (struct GNUNET_TESTING_PeerGroup *pg, enum GNUNET_TESTING_Topology topology, enum GNUNET_TESTING_Topology restrict_topology, const char *restrict_transports) { int ret; unsigned int num_connections; int unblacklisted_connections; char *filename; struct PeerConnection *conn_iter; struct PeerConnection *temp_conn; unsigned int off; #if !OLD unsigned int i; for (i = 0; i < pg->total; i++) { pg->peers[i].allowed_peers = GNUNET_CONTAINER_multihashmap_create (100); pg->peers[i].connect_peers = GNUNET_CONTAINER_multihashmap_create (100); pg->peers[i].blacklisted_peers = GNUNET_CONTAINER_multihashmap_create (100); pg->peers[i].pg = pg; } #endif switch (topology) { case GNUNET_TESTING_TOPOLOGY_CLIQUE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating clique topology\n")); num_connections = create_clique (pg, &add_connections, ALLOWED, GNUNET_NO); break; case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD_RING: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating small world (ring) topology\n")); num_connections = create_small_world_ring (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating small world (2d-torus) topology\n")); num_connections = create_small_world (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_RING: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating ring topology\n")); num_connections = create_ring (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_2D_TORUS: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating 2d torus topology\n")); num_connections = create_2d_torus (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_ERDOS_RENYI: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating Erdos-Renyi topology\n")); num_connections = create_erdos_renyi (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_INTERNAT: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating InterNAT topology\n")); num_connections = create_nated_internet (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_SCALE_FREE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating Scale Free topology\n")); num_connections = create_scale_free (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_LINE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating straight line topology\n")); num_connections = create_line (pg, &add_connections, ALLOWED); break; case GNUNET_TESTING_TOPOLOGY_FROM_FILE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating topology from file!\n")); if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (pg->cfg, "testing", "topology_file", &filename)) num_connections = create_from_file (pg, filename, &add_connections, ALLOWED); else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Missing configuration option TESTING:TOPOLOGY_FILE for creating topology from file!\n"); num_connections = 0; } break; case GNUNET_TESTING_TOPOLOGY_NONE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Creating no allowed topology (all peers can connect at core level)\n")); num_connections = pg->total * pg->total; /* Clique is allowed! */ break; default: num_connections = 0; break; } if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (pg->cfg, "TESTING", "F2F")) { ret = create_and_copy_friend_files (pg); if (ret != GNUNET_OK) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Failed during friend file copying!\n")); return GNUNET_SYSERR; } else { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Friend files created/copied successfully!\n")); } } /* Use the create clique method to initially set all connections as blacklisted. */ if ((restrict_topology != GNUNET_TESTING_TOPOLOGY_NONE) && (restrict_topology != GNUNET_TESTING_TOPOLOGY_FROM_FILE)) create_clique (pg, &add_connections, BLACKLIST, GNUNET_NO); else return num_connections; unblacklisted_connections = 0; /* Un-blacklist connections as per the topology specified */ switch (restrict_topology) { case GNUNET_TESTING_TOPOLOGY_CLIQUE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but clique topology\n")); unblacklisted_connections = create_clique (pg, &remove_connections, BLACKLIST, GNUNET_NO); break; case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD_RING: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but small world (ring) topology\n")); unblacklisted_connections = create_small_world_ring (pg, &remove_connections, BLACKLIST); break; case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but small world (2d-torus) topology\n")); unblacklisted_connections = create_small_world (pg, &remove_connections, BLACKLIST); break; case GNUNET_TESTING_TOPOLOGY_RING: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but ring topology\n")); unblacklisted_connections = create_ring (pg, &remove_connections, BLACKLIST); break; case GNUNET_TESTING_TOPOLOGY_2D_TORUS: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but 2d torus topology\n")); unblacklisted_connections = create_2d_torus (pg, &remove_connections, BLACKLIST); break; case GNUNET_TESTING_TOPOLOGY_ERDOS_RENYI: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but Erdos-Renyi topology\n")); unblacklisted_connections = create_erdos_renyi (pg, &remove_connections, BLACKLIST); break; case GNUNET_TESTING_TOPOLOGY_INTERNAT: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but InterNAT topology\n")); #if TOPOLOGY_HACK for (off = 0; off < pg->total; off++) { conn_iter = pg->peers[off].allowed_peers_head; while (conn_iter != NULL) { temp_conn = conn_iter->next; GNUNET_free (conn_iter); conn_iter = temp_conn; } pg->peers[off].allowed_peers_head = NULL; pg->peers[off].allowed_peers_tail = NULL; conn_iter = pg->peers[off].connect_peers_head; while (conn_iter != NULL) { temp_conn = conn_iter->next; GNUNET_free (conn_iter); conn_iter = temp_conn; } pg->peers[off].connect_peers_head = NULL; pg->peers[off].connect_peers_tail = NULL; } unblacklisted_connections = create_nated_internet_copy (pg, &remove_connections, BLACKLIST); #else unblacklisted_connections = create_nated_internet (pg, &remove_connections, BLACKLIST); #endif break; case GNUNET_TESTING_TOPOLOGY_SCALE_FREE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but Scale Free topology\n")); unblacklisted_connections = create_scale_free (pg, &remove_connections, BLACKLIST); break; case GNUNET_TESTING_TOPOLOGY_LINE: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklisting all but straight line topology\n")); unblacklisted_connections = create_line (pg, &remove_connections, BLACKLIST); default: break; } if ((unblacklisted_connections > 0) && (restrict_transports != NULL)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Creating blacklist with `%s'\n", restrict_transports); ret = create_and_copy_blacklist_files (pg, restrict_transports); if (ret != GNUNET_OK) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Failed during blacklist file copying!\n")); return 0; } else { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Blacklist files created/copied successfully!\n")); } } return num_connections; } #if !OLD /** * Iterator for choosing random peers to connect. * * @param cls closure, a RandomContext * @param key the key the second Daemon was stored under * @param value the GNUNET_TESTING_Daemon that the first is to connect to * * @return GNUNET_YES to continue iteration */ static int random_connect_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct RandomContext *random_ctx = cls; double random_number; uint32_t second_pos; GNUNET_HashCode first_hash; random_number = ((double) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX)) / ((double) UINT64_MAX); if (random_number < random_ctx->percentage) { GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (random_ctx-> first->connect_peers_working_set, key, value, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); } /* Now we have considered this particular connection, remove it from the second peer so it's not double counted */ uid_from_hash (key, &second_pos); hash_from_uid (random_ctx->first_uid, &first_hash); GNUNET_assert (random_ctx->pg->total > second_pos); GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (random_ctx-> pg->peers [second_pos].connect_peers, &first_hash, random_ctx-> first->daemon)); return GNUNET_YES; } /** * Iterator for adding at least X peers to a peers connection set. * * @param cls closure, MinimumContext * @param key the key the second Daemon was stored under * @param value the GNUNET_TESTING_Daemon that the first is to connect to * * @return GNUNET_YES to continue iteration */ static int minimum_connect_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct MinimumContext *min_ctx = cls; uint32_t second_pos; GNUNET_HashCode first_hash; unsigned int i; if (GNUNET_CONTAINER_multihashmap_size (min_ctx->first->connect_peers_working_set) < min_ctx->num_to_add) { for (i = 0; i < min_ctx->num_to_add; i++) { if (min_ctx->pg_array[i] == min_ctx->current) { GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (min_ctx-> first->connect_peers_working_set, key, value, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); uid_from_hash (key, &second_pos); hash_from_uid (min_ctx->first_uid, &first_hash); GNUNET_assert (min_ctx->pg->total > second_pos); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (min_ctx-> pg->peers [second_pos].connect_peers_working_set, &first_hash, min_ctx->first-> daemon, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); /* Now we have added this particular connection, remove it from the second peer's map so it's not double counted */ GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (min_ctx-> pg->peers [second_pos].connect_peers, &first_hash, min_ctx-> first->daemon)); } } min_ctx->current++; return GNUNET_YES; } else return GNUNET_NO; /* We can stop iterating, we have enough peers! */ } /** * Iterator for adding peers to a connection set based on a depth first search. * * @param cls closure, MinimumContext * @param key the key the second daemon was stored under * @param value the GNUNET_TESTING_Daemon that the first is to connect to * * @return GNUNET_YES to continue iteration */ static int dfs_connect_iterator (void *cls, const GNUNET_HashCode * key, void *value) { struct DFSContext *dfs_ctx = cls; GNUNET_HashCode first_hash; if (dfs_ctx->current == dfs_ctx->chosen) { GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (dfs_ctx-> first->connect_peers_working_set, key, value, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); uid_from_hash (key, &dfs_ctx->second_uid); hash_from_uid (dfs_ctx->first_uid, &first_hash); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (dfs_ctx-> pg->peers[dfs_ctx-> second_uid].connect_peers_working_set, &first_hash, dfs_ctx->first->daemon, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (dfs_ctx-> pg->peers [dfs_ctx->second_uid].connect_peers, &first_hash, dfs_ctx-> first->daemon)); /* Can't remove second from first yet because we are currently iterating, hence the return value in the DFSContext! */ return GNUNET_NO; /* We have found our peer, don't iterate more */ } dfs_ctx->current++; return GNUNET_YES; } #endif /** * From the set of connections possible, choose percentage percent of connections * to actually connect. * * @param pg the peergroup we are dealing with * @param percentage what percent of total connections to make */ void choose_random_connections (struct GNUNET_TESTING_PeerGroup *pg, double percentage) { uint32_t pg_iter; #if OLD struct PeerConnection *conn_iter; double random_number; #else struct RandomContext random_ctx; #endif for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if OLD conn_iter = pg->peers[pg_iter].connect_peers_head; while (conn_iter != NULL) { random_number = ((double) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX)) / ((double) UINT64_MAX); if (random_number < percentage) { add_connections (pg, pg_iter, conn_iter->index, WORKING_SET, GNUNET_YES); } conn_iter = conn_iter->next; } #else random_ctx.first_uid = pg_iter; random_ctx.first = &pg->peers[pg_iter]; random_ctx.percentage = percentage; random_ctx.pg = pg; pg->peers[pg_iter].connect_peers_working_set = GNUNET_CONTAINER_multihashmap_create (pg->total); GNUNET_CONTAINER_multihashmap_iterate (pg->peers[pg_iter].connect_peers, &random_connect_iterator, &random_ctx); /* Now remove the old connections */ GNUNET_CONTAINER_multihashmap_destroy (pg->peers[pg_iter].connect_peers); /* And replace with the random set */ pg->peers[pg_iter].connect_peers = pg->peers[pg_iter].connect_peers_working_set; #endif } for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { conn_iter = pg->peers[pg_iter].connect_peers_head; while (pg->peers[pg_iter].connect_peers_head != NULL) remove_connections (pg, pg_iter, pg->peers[pg_iter].connect_peers_head->index, CONNECT, GNUNET_YES); pg->peers[pg_iter].connect_peers_head = pg->peers[pg_iter].connect_peers_working_set_head; pg->peers[pg_iter].connect_peers_tail = pg->peers[pg_iter].connect_peers_working_set_tail; pg->peers[pg_iter].connect_peers_working_set_head = NULL; pg->peers[pg_iter].connect_peers_working_set_tail = NULL; } } /** * Count the number of connections in a linked list of connections. * * @param conn_list the connection list to get the count of * * @return the number of elements in the list */ static unsigned int count_connections (struct PeerConnection *conn_list) { struct PeerConnection *iter; unsigned int count; count = 0; iter = conn_list; while (iter != NULL) { iter = iter->next; count++; } return count; } static unsigned int count_workingset_connections (struct GNUNET_TESTING_PeerGroup *pg) { unsigned int count; unsigned int pg_iter; #if OLD struct PeerConnection *conn_iter; #endif count = 0; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if OLD conn_iter = pg->peers[pg_iter].connect_peers_working_set_head; while (conn_iter != NULL) { count++; conn_iter = conn_iter->next; } #else count += GNUNET_CONTAINER_multihashmap_size (pg-> peers [pg_iter].connect_peers_working_set); #endif } return count; } static unsigned int count_allowed_connections (struct GNUNET_TESTING_PeerGroup *pg) { unsigned int count; unsigned int pg_iter; #if OLD struct PeerConnection *conn_iter; #endif count = 0; for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { #if OLD conn_iter = pg->peers[pg_iter].allowed_peers_head; while (conn_iter != NULL) { count++; conn_iter = conn_iter->next; } #else count += GNUNET_CONTAINER_multihashmap_size (pg->peers[pg_iter].allowed_peers); #endif } return count; } /** * From the set of connections possible, choose at least num connections per * peer. * * @param pg the peergroup we are dealing with * @param num how many connections at least should each peer have (if possible)? */ static void choose_minimum (struct GNUNET_TESTING_PeerGroup *pg, unsigned int num) { #if !OLD struct MinimumContext minimum_ctx; #else struct PeerConnection *conn_iter; unsigned int temp_list_size; unsigned int i; unsigned int count; uint32_t random; /* Random list entry to connect peer to */ #endif uint32_t pg_iter; #if OLD for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { temp_list_size = count_connections (pg->peers[pg_iter].connect_peers_head); if (temp_list_size == 0) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Peer %d has 0 connections!?!?\n", pg_iter); break; } for (i = 0; i < num; i++) { random = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, temp_list_size); conn_iter = pg->peers[pg_iter].connect_peers_head; for (count = 0; count < random; count++) conn_iter = conn_iter->next; /* We now have a random connection, connect it! */ GNUNET_assert (conn_iter != NULL); add_connections (pg, pg_iter, conn_iter->index, WORKING_SET, GNUNET_YES); } } #else for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { pg->peers[pg_iter].connect_peers_working_set = GNUNET_CONTAINER_multihashmap_create (num); } for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { minimum_ctx.first_uid = pg_iter; minimum_ctx.pg_array = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK, GNUNET_CONTAINER_multihashmap_size (pg->peers[pg_iter].connect_peers)); minimum_ctx.first = &pg->peers[pg_iter]; minimum_ctx.pg = pg; minimum_ctx.num_to_add = num; minimum_ctx.current = 0; GNUNET_CONTAINER_multihashmap_iterate (pg->peers[pg_iter].connect_peers, &minimum_connect_iterator, &minimum_ctx); } for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { /* Remove the "old" connections */ GNUNET_CONTAINER_multihashmap_destroy (pg->peers[pg_iter].connect_peers); /* And replace with the working set */ pg->peers[pg_iter].connect_peers = pg->peers[pg_iter].connect_peers_working_set; } #endif for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { while (pg->peers[pg_iter].connect_peers_head != NULL) { conn_iter = pg->peers[pg_iter].connect_peers_head; GNUNET_CONTAINER_DLL_remove (pg->peers[pg_iter].connect_peers_head, pg->peers[pg_iter].connect_peers_tail, conn_iter); GNUNET_free (conn_iter); /*remove_connections(pg, pg_iter, pg->peers[pg_iter].connect_peers_head->index, CONNECT, GNUNET_YES); */ } pg->peers[pg_iter].connect_peers_head = pg->peers[pg_iter].connect_peers_working_set_head; pg->peers[pg_iter].connect_peers_tail = pg->peers[pg_iter].connect_peers_working_set_tail; pg->peers[pg_iter].connect_peers_working_set_head = NULL; pg->peers[pg_iter].connect_peers_working_set_tail = NULL; } } #if !OLD struct FindClosestContext { /** * The currently known closest peer. */ struct GNUNET_TESTING_Daemon *closest; /** * The info for the peer we are adding connections for. */ struct PeerData *curr_peer; /** * The distance (bits) between the current * peer and the currently known closest. */ unsigned int closest_dist; /** * The offset of the closest known peer in * the peer group. */ unsigned int closest_num; }; /** * Iterator over hash map entries of the allowed * peer connections. Find the closest, not already * connected peer and return it. * * @param cls closure (struct FindClosestContext) * @param key current key code (hash of offset in pg) * @param value value in the hash map - a GNUNET_TESTING_Daemon * @return GNUNET_YES if we should continue to * iterate, * GNUNET_NO if not. */ static int find_closest_peers (void *cls, const GNUNET_HashCode * key, void *value) { struct FindClosestContext *closest_ctx = cls; struct GNUNET_TESTING_Daemon *daemon = value; if (((closest_ctx->closest == NULL) || (GNUNET_CRYPTO_hash_matching_bits (&daemon->id.hashPubKey, &closest_ctx->curr_peer->daemon->id.hashPubKey) > closest_ctx->closest_dist)) && (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains (closest_ctx-> curr_peer->connect_peers, key))) { closest_ctx->closest_dist = GNUNET_CRYPTO_hash_matching_bits (&daemon->id.hashPubKey, &closest_ctx->curr_peer->daemon-> id.hashPubKey); closest_ctx->closest = daemon; uid_from_hash (key, &closest_ctx->closest_num); } return GNUNET_YES; } /** * From the set of connections possible, choose at num connections per * peer based on depth which are closest out of those allowed. Guaranteed * to add num peers to connect to, provided there are that many peers * in the underlay topology to connect to. * * @param pg the peergroup we are dealing with * @param num how many connections at least should each peer have (if possible)? * @param proc processor to actually add the connections * @param list the peer list to use */ void add_closest (struct GNUNET_TESTING_PeerGroup *pg, unsigned int num, GNUNET_TESTING_ConnectionProcessor proc, enum PeerLists list) { #if OLD #else struct FindClosestContext closest_ctx; #endif uint32_t pg_iter; uint32_t i; for (i = 0; i < num; i++) /* Each time find a closest peer (from those available) */ { for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { closest_ctx.curr_peer = &pg->peers[pg_iter]; closest_ctx.closest = NULL; closest_ctx.closest_dist = 0; closest_ctx.closest_num = 0; GNUNET_CONTAINER_multihashmap_iterate (pg->peers[pg_iter].allowed_peers, &find_closest_peers, &closest_ctx); if (closest_ctx.closest != NULL) { GNUNET_assert (closest_ctx.closest_num < pg->total); proc (pg, pg_iter, closest_ctx.closest_num, list); } } } } #endif /** * From the set of connections possible, choose at least num connections per * peer based on depth first traversal of peer connections. If DFS leaves * peers unconnected, ensure those peers get connections. * * @param pg the peergroup we are dealing with * @param num how many connections at least should each peer have (if possible)? */ void perform_dfs (struct GNUNET_TESTING_PeerGroup *pg, unsigned int num) { uint32_t pg_iter; uint32_t dfs_count; uint32_t starting_peer; uint32_t least_connections; uint32_t random_connection; #if OLD unsigned int temp_count; struct PeerConnection *peer_iter; #else struct DFSContext dfs_ctx; GNUNET_HashCode second_hash; #endif #if OLD starting_peer = 0; dfs_count = 0; while ((count_workingset_connections (pg) < num * pg->total) && (count_allowed_connections (pg) > 0)) { if (dfs_count % pg->total == 0) /* Restart the DFS at some weakly connected peer */ { least_connections = -1; /* Set to very high number */ for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { temp_count = count_connections (pg-> peers[pg_iter].connect_peers_working_set_head); if (temp_count < least_connections) { starting_peer = pg_iter; least_connections = temp_count; } } } temp_count = count_connections (pg->peers[starting_peer].connect_peers_head); if (temp_count == 0) continue; /* FIXME: infinite loop? */ random_connection = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, temp_count); temp_count = 0; peer_iter = pg->peers[starting_peer].connect_peers_head; while (temp_count < random_connection) { peer_iter = peer_iter->next; temp_count++; } GNUNET_assert (peer_iter != NULL); add_connections (pg, starting_peer, peer_iter->index, WORKING_SET, GNUNET_NO); remove_connections (pg, starting_peer, peer_iter->index, CONNECT, GNUNET_YES); starting_peer = peer_iter->index; dfs_count++; } #else for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { pg->peers[pg_iter].connect_peers_working_set = GNUNET_CONTAINER_multihashmap_create (num); } starting_peer = 0; dfs_count = 0; while ((count_workingset_connections (pg) < num * pg->total) && (count_allowed_connections (pg) > 0)) { if (dfs_count % pg->total == 0) /* Restart the DFS at some weakly connected peer */ { least_connections = -1; /* Set to very high number */ for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { if (GNUNET_CONTAINER_multihashmap_size (pg->peers[pg_iter].connect_peers_working_set) < least_connections) { starting_peer = pg_iter; least_connections = GNUNET_CONTAINER_multihashmap_size (pg-> peers [pg_iter].connect_peers_working_set); } } } if (GNUNET_CONTAINER_multihashmap_size (pg->peers[starting_peer].connect_peers) == 0) /* Ensure there is at least one peer left to connect! */ { dfs_count = 0; continue; } /* Choose a random peer from the chosen peers set of connections to add */ dfs_ctx.chosen = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, GNUNET_CONTAINER_multihashmap_size (pg->peers [starting_peer].connect_peers)); dfs_ctx.first_uid = starting_peer; dfs_ctx.first = &pg->peers[starting_peer]; dfs_ctx.pg = pg; dfs_ctx.current = 0; GNUNET_CONTAINER_multihashmap_iterate (pg-> peers[starting_peer].connect_peers, &dfs_connect_iterator, &dfs_ctx); /* Remove the second from the first, since we will be continuing the search and may encounter the first peer again! */ hash_from_uid (dfs_ctx.second_uid, &second_hash); GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (pg->peers [starting_peer].connect_peers, &second_hash, pg-> peers [dfs_ctx.second_uid].daemon)); starting_peer = dfs_ctx.second_uid; } for (pg_iter = 0; pg_iter < pg->total; pg_iter++) { /* Remove the "old" connections */ GNUNET_CONTAINER_multihashmap_destroy (pg->peers[pg_iter].connect_peers); /* And replace with the working set */ pg->peers[pg_iter].connect_peers = pg->peers[pg_iter].connect_peers_working_set; } #endif } /** * Internal callback for topology information for a particular peer. */ static void internal_topology_callback (void *cls, const struct GNUNET_PeerIdentity *peer, const struct GNUNET_ATS_Information *atsi, unsigned int atsi_count) { struct CoreContext *core_ctx = cls; struct TopologyIterateContext *iter_ctx = core_ctx->iter_context; if (peer == NULL) /* Either finished, or something went wrong */ { iter_ctx->completed++; iter_ctx->connected--; /* One core context allocated per iteration, must free! */ GNUNET_free (core_ctx); } else { iter_ctx->topology_cb (iter_ctx->cls, &core_ctx->daemon->id, peer, NULL); } if (iter_ctx->completed == iter_ctx->total) { iter_ctx->topology_cb (iter_ctx->cls, NULL, NULL, NULL); /* Once all are done, free the iteration context */ GNUNET_free (iter_ctx); } } /** * Check running topology iteration tasks, if below max start a new one, otherwise * schedule for some time in the future. */ static void schedule_get_topology (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct CoreContext *core_context = cls; struct TopologyIterateContext *topology_context = (struct TopologyIterateContext *) core_context->iter_context; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) return; if (topology_context->connected > topology_context->pg->max_outstanding_connections) { #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Delaying connect, we have too many outstanding connections!\n")); #endif GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_get_topology, core_context); } else { #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating connection, outstanding_connections is %d\n"), outstanding_connects); #endif topology_context->connected++; if (GNUNET_OK != GNUNET_CORE_iterate_peers (core_context->daemon->cfg, &internal_topology_callback, core_context)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Topology iteration failed.\n"); internal_topology_callback (core_context, NULL, NULL, 0); } } } /** * Iterate over all (running) peers in the peer group, retrieve * all connections that each currently has. */ void GNUNET_TESTING_get_topology (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_NotifyTopology cb, void *cls) { struct TopologyIterateContext *topology_context; struct CoreContext *core_ctx; unsigned int i; unsigned int total_count; /* Allocate a single topology iteration context */ topology_context = GNUNET_malloc (sizeof (struct TopologyIterateContext)); topology_context->topology_cb = cb; topology_context->cls = cls; topology_context->pg = pg; total_count = 0; for (i = 0; i < pg->total; i++) { if (pg->peers[i].daemon->running == GNUNET_YES) { /* Allocate one core context per core we need to connect to */ core_ctx = GNUNET_malloc (sizeof (struct CoreContext)); core_ctx->daemon = pg->peers[i].daemon; /* Set back pointer to topology iteration context */ core_ctx->iter_context = topology_context; GNUNET_SCHEDULER_add_now (&schedule_get_topology, core_ctx); total_count++; } } if (total_count == 0) { cb (cls, NULL, NULL, "Cannot iterate over topology, no running peers!"); GNUNET_free (topology_context); } else topology_context->total = total_count; return; } /** * Callback function to process statistic values. * This handler is here only really to insert a peer * identity (or daemon) so the statistics can be uniquely * tied to a single running peer. * * @param cls closure * @param subsystem name of subsystem that created the statistic * @param name the name of the datum * @param value the current value * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration */ static int internal_stats_callback (void *cls, const char *subsystem, const char *name, uint64_t value, int is_persistent) { struct StatsCoreContext *core_context = cls; struct StatsIterateContext *stats_context = (struct StatsIterateContext *) core_context->iter_context; return stats_context->proc (stats_context->cls, &core_context->daemon->id, subsystem, name, value, is_persistent); } /** * Internal continuation call for statistics iteration. * * @param cls closure, the CoreContext for this iteration * @param success whether or not the statistics iterations * was canceled or not (we don't care) */ static void internal_stats_cont (void *cls, int success) { struct StatsCoreContext *core_context = cls; struct StatsIterateContext *stats_context = (struct StatsIterateContext *) core_context->iter_context; stats_context->connected--; stats_context->completed++; if (stats_context->completed == stats_context->total) { stats_context->cont (stats_context->cls, GNUNET_YES); GNUNET_free (stats_context); } if (core_context->stats_handle != NULL) GNUNET_STATISTICS_destroy (core_context->stats_handle, GNUNET_NO); GNUNET_free (core_context); } /** * Check running topology iteration tasks, if below max start a new one, otherwise * schedule for some time in the future. */ static void schedule_get_statistics (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct StatsCoreContext *core_context = cls; struct StatsIterateContext *stats_context = (struct StatsIterateContext *) core_context->iter_context; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) return; if (stats_context->connected > stats_context->pg->max_outstanding_connections) { #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Delaying connect, we have too many outstanding connections!\n")); #endif GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_get_statistics, core_context); } else { #if VERBOSE_TESTING > 2 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating connection, outstanding_connections is %d\n"), outstanding_connects); #endif stats_context->connected++; core_context->stats_handle = GNUNET_STATISTICS_create ("testing", core_context->daemon->cfg); if (core_context->stats_handle == NULL) { internal_stats_cont (core_context, GNUNET_NO); return; } core_context->stats_get_handle = GNUNET_STATISTICS_get (core_context->stats_handle, NULL, NULL, GNUNET_TIME_relative_get_forever (), &internal_stats_cont, &internal_stats_callback, core_context); if (core_context->stats_get_handle == NULL) internal_stats_cont (core_context, GNUNET_NO); } } struct DuplicateStats { /** * Next item in the list */ struct DuplicateStats *next; /** * Nasty string, concatenation of relevant information. */ char *unique_string; }; /** * Check whether the combination of port/host/unix domain socket * already exists in the list of peers being checked for statistics. * * @param pg the peergroup in question * @param specific_peer the peer we're concerned with * @param stats_list the list to return to the caller * * @return GNUNET_YES if the statistics instance has been seen already, * GNUNET_NO if not (and we may have added it to the list) */ static int stats_check_existing (struct GNUNET_TESTING_PeerGroup *pg, struct PeerData *specific_peer, struct DuplicateStats **stats_list) { struct DuplicateStats *pos; char *unix_domain_socket; unsigned long long port; char *to_match; if (GNUNET_YES != GNUNET_CONFIGURATION_get_value_yesno (pg->cfg, "testing", "single_statistics_per_host")) return GNUNET_NO; /* Each peer has its own statistics instance, do nothing! */ pos = *stats_list; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (specific_peer->cfg, "statistics", "unixpath", &unix_domain_socket)) return GNUNET_NO; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (specific_peer->cfg, "statistics", "port", &port)) { GNUNET_free (unix_domain_socket); return GNUNET_NO; } if (specific_peer->daemon->hostname != NULL) GNUNET_asprintf (&to_match, "%s%s%llu", specific_peer->daemon->hostname, unix_domain_socket, port); else GNUNET_asprintf (&to_match, "%s%llu", unix_domain_socket, port); while (pos != NULL) { if (0 == strcmp (to_match, pos->unique_string)) { GNUNET_free (unix_domain_socket); GNUNET_free (to_match); return GNUNET_YES; } pos = pos->next; } pos = GNUNET_malloc (sizeof (struct DuplicateStats)); pos->unique_string = to_match; pos->next = *stats_list; *stats_list = pos; GNUNET_free (unix_domain_socket); return GNUNET_NO; } /** * Iterate over all (running) peers in the peer group, retrieve * all statistics from each. * * @param pg the peergroup to iterate statistics of * @param cont continuation to call once all stats have been retrieved * @param proc processing function for each statistic from each peer * @param cls closure to pass to proc * */ void GNUNET_TESTING_get_statistics (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_STATISTICS_Callback cont, GNUNET_TESTING_STATISTICS_Iterator proc, void *cls) { struct StatsIterateContext *stats_context; struct StatsCoreContext *core_ctx; unsigned int i; unsigned int total_count; struct DuplicateStats *stats_list; struct DuplicateStats *pos; stats_list = NULL; /* Allocate a single stats iteration context */ stats_context = GNUNET_malloc (sizeof (struct StatsIterateContext)); stats_context->cont = cont; stats_context->proc = proc; stats_context->cls = cls; stats_context->pg = pg; total_count = 0; for (i = 0; i < pg->total; i++) { if ((pg->peers[i].daemon->running == GNUNET_YES) && (GNUNET_NO == stats_check_existing (pg, &pg->peers[i], &stats_list))) { /* Allocate one core context per core we need to connect to */ core_ctx = GNUNET_malloc (sizeof (struct StatsCoreContext)); core_ctx->daemon = pg->peers[i].daemon; /* Set back pointer to topology iteration context */ core_ctx->iter_context = stats_context; GNUNET_SCHEDULER_add_now (&schedule_get_statistics, core_ctx); total_count++; } } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Retrieving stats from %u total instances.\n", total_count); if (0 != total_count) stats_context->total = total_count; else GNUNET_free (stats_context); if (stats_list != NULL) { pos = stats_list; while (pos != NULL) { GNUNET_free (pos->unique_string); stats_list = pos->next; GNUNET_free (pos); pos = stats_list->next; } } return; } /** * Stop the connection process temporarily. * * @param pg the peer group to stop connecting */ void GNUNET_TESTING_stop_connections (struct GNUNET_TESTING_PeerGroup *pg) { pg->stop_connects = GNUNET_YES; } /** * Resume the connection process temporarily. * * @param pg the peer group to resume connecting */ void GNUNET_TESTING_resume_connections (struct GNUNET_TESTING_PeerGroup *pg) { pg->stop_connects = GNUNET_NO; } /** * There are many ways to connect peers that are supported by this function. * To connect peers in the same topology that was created via the * GNUNET_TESTING_create_topology, the topology variable must be set to * GNUNET_TESTING_TOPOLOGY_NONE. If the topology variable is specified, * a new instance of that topology will be generated and attempted to be * connected. This could result in some connections being impossible, * because some topologies are non-deterministic. * * @param pg the peer group struct representing the running peers * @param topology which topology to connect the peers in * @param options options for connecting the topology * @param option_modifier modifier for options that take a parameter * @param connect_timeout how long to wait before giving up on connecting * two peers * @param connect_attempts how many times to attempt to connect two peers * over the connect_timeout duration * @param notify_callback notification to be called once all connections completed * @param notify_cls closure for notification callback * * @return the number of connections that will be attempted, GNUNET_SYSERR on error */ int GNUNET_TESTING_connect_topology (struct GNUNET_TESTING_PeerGroup *pg, enum GNUNET_TESTING_Topology topology, enum GNUNET_TESTING_TopologyOption options, double option_modifier, struct GNUNET_TIME_Relative connect_timeout, unsigned int connect_attempts, GNUNET_TESTING_NotifyCompletion notify_callback, void *notify_cls) { switch (topology) { case GNUNET_TESTING_TOPOLOGY_CLIQUE: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating clique CONNECT topology\n")); #endif create_clique (pg, &add_connections, CONNECT, GNUNET_NO); break; case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD_RING: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating small world (ring) CONNECT topology\n")); #endif create_small_world_ring (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_SMALL_WORLD: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating small world (2d-torus) CONNECT topology\n")); #endif create_small_world (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_RING: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating ring CONNECT topology\n")); #endif create_ring (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_2D_TORUS: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating 2d torus CONNECT topology\n")); #endif create_2d_torus (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_ERDOS_RENYI: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating Erdos-Renyi CONNECT topology\n")); #endif create_erdos_renyi (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_INTERNAT: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating InterNAT CONNECT topology\n")); #endif create_nated_internet (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_SCALE_FREE: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating Scale Free CONNECT topology\n")); #endif create_scale_free (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_LINE: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating straight line CONNECT topology\n")); #endif create_line (pg, &add_connections, CONNECT); break; case GNUNET_TESTING_TOPOLOGY_NONE: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Creating no CONNECT topology\n")); #endif copy_allowed_topology (pg); break; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Unknown topology specification, can't connect peers!\n")); return GNUNET_SYSERR; } switch (options) { case GNUNET_TESTING_TOPOLOGY_OPTION_RANDOM: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Connecting random subset (%'.2f percent) of possible peers\n"), 100 * option_modifier); #endif choose_random_connections (pg, option_modifier); break; case GNUNET_TESTING_TOPOLOGY_OPTION_MINIMUM: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Connecting a minimum of %u peers each (if possible)\n"), (unsigned int) option_modifier); #endif choose_minimum (pg, (unsigned int) option_modifier); break; case GNUNET_TESTING_TOPOLOGY_OPTION_DFS: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _ ("Using DFS to connect a minimum of %u peers each (if possible)\n"), (unsigned int) option_modifier); #endif #if FIXME perform_dfs (pg, (int) option_modifier); #endif break; case GNUNET_TESTING_TOPOLOGY_OPTION_ADD_CLOSEST: #if VERBOSE_TOPOLOGY GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Finding additional %u closest peers each (if possible)\n"), (unsigned int) option_modifier); #endif #if FIXME add_closest (pg, (unsigned int) option_modifier, &add_connections, CONNECT); #endif break; case GNUNET_TESTING_TOPOLOGY_OPTION_NONE: break; case GNUNET_TESTING_TOPOLOGY_OPTION_ALL: break; default: break; } return connect_topology (pg, connect_timeout, connect_attempts, notify_callback, notify_cls); } /** * Lookup and return the number of SSH connections to a host. * * @param hostname the hostname to lookup in the list * @param pg the peergroup that the host belongs to * * @return the number of current ssh connections to the host */ static unsigned int count_outstanding_at_host (const char *hostname, struct GNUNET_TESTING_PeerGroup *pg) { struct OutstandingSSH *pos; pos = pg->ssh_head; while ((pos != NULL) && (strcmp (pos->hostname, hostname) != 0)) pos = pos->next; GNUNET_assert (pos != NULL); return pos->outstanding; } /** * Increment the number of SSH connections to a host by one. * * @param hostname the hostname to lookup in the list * @param pg the peergroup that the host belongs to * */ static void increment_outstanding_at_host (const char *hostname, struct GNUNET_TESTING_PeerGroup *pg) { struct OutstandingSSH *pos; pos = pg->ssh_head; while ((pos != NULL) && (strcmp (pos->hostname, hostname) != 0)) pos = pos->next; GNUNET_assert (pos != NULL); pos->outstanding++; } /** * Decrement the number of SSH connections to a host by one. * * @param hostname the hostname to lookup in the list * @param pg the peergroup that the host belongs to * */ static void decrement_outstanding_at_host (const char *hostname, struct GNUNET_TESTING_PeerGroup *pg) { struct OutstandingSSH *pos; pos = pg->ssh_head; while ((pos != NULL) && (strcmp (pos->hostname, hostname) != 0)) pos = pos->next; GNUNET_assert (pos != NULL); pos->outstanding--; } /** * Callback that is called whenever a hostkey is generated * for a peer. Call the real callback and decrement the * starting counter for the peergroup. * * @param cls closure * @param id identifier for the daemon, NULL on error * @param d handle for the daemon * @param emsg error message (NULL on success) */ static void internal_hostkey_callback (void *cls, const struct GNUNET_PeerIdentity *id, struct GNUNET_TESTING_Daemon *d, const char *emsg) { struct InternalStartContext *internal_context = cls; internal_context->peer->pg->starting--; internal_context->peer->pg->started++; if (internal_context->hostname != NULL) decrement_outstanding_at_host (internal_context->hostname, internal_context->peer->pg); if (internal_context->hostkey_callback != NULL) internal_context->hostkey_callback (internal_context->hostkey_cls, id, d, emsg); else if (internal_context->peer->pg->started == internal_context->peer->pg->total) { internal_context->peer->pg->started = 0; /* Internal startup may use this counter! */ GNUNET_TESTING_daemons_continue_startup (internal_context->peer->pg); } } /** * Callback that is called whenever a peer has finished starting. * Call the real callback and decrement the starting counter * for the peergroup. * * @param cls closure * @param id identifier for the daemon, NULL on error * @param cfg config * @param d handle for the daemon * @param emsg error message (NULL on success) */ static void internal_startup_callback (void *cls, const struct GNUNET_PeerIdentity *id, const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_TESTING_Daemon *d, const char *emsg) { struct InternalStartContext *internal_context = cls; internal_context->peer->pg->starting--; if (internal_context->hostname != NULL) decrement_outstanding_at_host (internal_context->hostname, internal_context->peer->pg); if (internal_context->start_cb != NULL) internal_context->start_cb (internal_context->start_cb_cls, id, cfg, d, emsg); } /** * Calls GNUNET_TESTING_daemon_continue_startup to set the daemon's state * from HOSTKEY_CREATED to TOPOLOGY_SETUP. Makes sure not to saturate a host * with requests delaying them when needed. * * @param cls closure: internal context of the daemon. * @param tc TaskContext */ static void internal_continue_startup (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct InternalStartContext *internal_context = cls; internal_context->peer->startup_task = GNUNET_SCHEDULER_NO_TASK; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) { return; } if ((internal_context->peer->pg->starting < internal_context->peer->pg->max_concurrent_ssh) || ((internal_context->hostname != NULL) && (count_outstanding_at_host (internal_context->hostname, internal_context->peer->pg) < internal_context->peer->pg->max_concurrent_ssh))) { if (internal_context->hostname != NULL) increment_outstanding_at_host (internal_context->hostname, internal_context->peer->pg); internal_context->peer->pg->starting++; GNUNET_TESTING_daemon_continue_startup (internal_context->peer->daemon); } else { internal_context->peer->startup_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &internal_continue_startup, internal_context); } } /** * Callback for informing us about a successful * or unsuccessful churn start call. * * @param cls a ChurnContext * @param id the peer identity of the started peer * @param cfg the handle to the configuration of the peer * @param d handle to the daemon for the peer * @param emsg NULL on success, non-NULL on failure * */ void churn_start_callback (void *cls, const struct GNUNET_PeerIdentity *id, const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_TESTING_Daemon *d, const char *emsg) { struct ChurnRestartContext *startup_ctx = cls; struct ChurnContext *churn_ctx = startup_ctx->churn_ctx; unsigned int total_left; char *error_message; error_message = NULL; if (emsg != NULL) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Churn stop callback failed with error `%s'\n", emsg); churn_ctx->num_failed_start++; } else { churn_ctx->num_to_start--; } total_left = (churn_ctx->num_to_stop - churn_ctx->num_failed_stop) + (churn_ctx->num_to_start - churn_ctx->num_failed_start); if (total_left == 0) { if ((churn_ctx->num_failed_stop > 0) || (churn_ctx->num_failed_start > 0)) GNUNET_asprintf (&error_message, "Churn didn't complete successfully, %u peers failed to start %u peers failed to be stopped!", churn_ctx->num_failed_start, churn_ctx->num_failed_stop); churn_ctx->cb (churn_ctx->cb_cls, error_message); GNUNET_free_non_null (error_message); GNUNET_free (churn_ctx); GNUNET_free (startup_ctx); } } static void schedule_churn_restart (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct PeerRestartContext *peer_restart_ctx = cls; struct ChurnRestartContext *startup_ctx = peer_restart_ctx->churn_restart_ctx; if (startup_ctx->outstanding > startup_ctx->pg->max_concurrent_ssh) GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_churn_restart, peer_restart_ctx); else { if (startup_ctx->churn_ctx->service != NULL) GNUNET_TESTING_daemon_start_stopped_service (peer_restart_ctx->daemon, startup_ctx-> churn_ctx->service, startup_ctx->timeout, &churn_start_callback, startup_ctx); else GNUNET_TESTING_daemon_start_stopped (peer_restart_ctx->daemon, startup_ctx->timeout, &churn_start_callback, startup_ctx); GNUNET_free (peer_restart_ctx); } } /** * Callback for informing us about a successful * or unsuccessful churn start call. * * @param cls a struct ServiceStartContext *startup_ctx * @param id the peer identity of the started peer * @param cfg the handle to the configuration of the peer * @param d handle to the daemon for the peer * @param emsg NULL on success, non-NULL on failure * */ void service_start_callback (void *cls, const struct GNUNET_PeerIdentity *id, const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_TESTING_Daemon *d, const char *emsg) { struct ServiceStartContext *startup_ctx = (struct ServiceStartContext *) cls; if (emsg != NULL) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Service start failed with error `%s'\n", emsg); } startup_ctx->outstanding--; startup_ctx->remaining--; if (startup_ctx->remaining == 0) { startup_ctx->cb (startup_ctx->cb_cls, NULL); GNUNET_free (startup_ctx->service); GNUNET_free (startup_ctx); } } static void schedule_service_start (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct PeerServiceStartContext *peer_ctx = cls; struct ServiceStartContext *startup_ctx = peer_ctx->start_ctx; if (startup_ctx->outstanding > startup_ctx->pg->max_concurrent_ssh) GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_service_start, peer_ctx); else { GNUNET_TESTING_daemon_start_service (peer_ctx->daemon, startup_ctx->service, startup_ctx->timeout, &service_start_callback, startup_ctx); GNUNET_free (peer_ctx); } } static void internal_start (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct InternalStartContext *internal_context = cls; if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) { return; } if ((internal_context->peer->pg->starting < internal_context->peer->pg->max_concurrent_ssh) || ((internal_context->hostname != NULL) && (count_outstanding_at_host (internal_context->hostname, internal_context->peer->pg) < internal_context->peer->pg->max_concurrent_ssh))) { if (internal_context->hostname != NULL) increment_outstanding_at_host (internal_context->hostname, internal_context->peer->pg); internal_context->peer->pg->starting++; internal_context->peer->daemon = GNUNET_TESTING_daemon_start (internal_context->peer->cfg, internal_context->timeout, GNUNET_NO, internal_context->hostname, internal_context->username, internal_context->sshport, internal_context->hostkey, &internal_hostkey_callback, internal_context, &internal_startup_callback, internal_context); } else { GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &internal_start, internal_context); } } #if USE_START_HELPER struct PeerStartHelperContext { struct GNUNET_TESTING_PeerGroup *pg; struct HostData *host; struct GNUNET_OS_Process *proc; }; static void check_peers_started (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct PeerStartHelperContext *helper = cls; enum GNUNET_OS_ProcessStatusType type; unsigned long code; unsigned int i; GNUNET_TESTING_NotifyDaemonRunning cb; if (GNUNET_NO == GNUNET_OS_process_status (helper->proc, &type, &code)) /* Still running, wait some more! */ { GNUNET_SCHEDULER_add_delayed (GNUNET_CONSTANTS_EXEC_WAIT, &check_peers_started, helper); return; } helper->pg->starting--; if (helper->pg->starting == 0) /* All peers have finished starting! */ { /* Call the peer started callback for each peer, set proper FSM state (?) */ for (i = 0; i < helper->pg->total; i++) { cb = helper->pg->peers[i].daemon->cb; helper->pg->peers[i].daemon->cb = NULL; helper->pg->peers[i].daemon->running = GNUNET_YES; helper->pg->peers[i].daemon->phase = SP_START_DONE; if (NULL != cb) { if ((type != GNUNET_OS_PROCESS_EXITED) || (code != 0)) cb (helper->pg->peers[i].daemon->cb_cls, &helper->pg->peers[i].daemon->id, helper->pg->peers[i].daemon->cfg, helper->pg->peers[i].daemon, "Failed to execute peerStartHelper.pl, or return code bad!"); else cb (helper->pg->peers[i].daemon->cb_cls, &helper->pg->peers[i].daemon->id, helper->pg->peers[i].daemon->cfg, helper->pg->peers[i].daemon, NULL); } } } GNUNET_OS_process_close (helper->proc); } static void start_peer_helper (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct PeerStartHelperContext *helper = cls; char *baseservicehome; char *tempdir; char *arg; /* ssh user@host peerStartHelper /path/to/basedirectory */ GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (helper->pg->cfg, "PATHS", "SERVICEHOME", &baseservicehome)); GNUNET_asprintf (&tempdir, "%s/%s/", baseservicehome, helper->host->hostname); if (NULL != helper->host->username) GNUNET_asprintf (&arg, "%s@%s", helper->host->username, helper->host->hostname); else GNUNET_asprintf (&arg, "%s", helper->host->hostname); /* FIXME: Doesn't support ssh_port option! */ helper->proc = GNUNET_OS_start_process (GNUNET_NO, NULL, NULL, "ssh", "ssh", arg, "peerStartHelper.pl", tempdir, NULL); GNUNET_assert (helper->proc != NULL); #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "starting peers with cmd ssh %s %s %s\n", arg, "peerStartHelper.pl", tempdir); #endif GNUNET_SCHEDULER_add_now (&check_peers_started, helper); GNUNET_free (tempdir); GNUNET_free (baseservicehome); GNUNET_free (arg); } #endif /** * Function which continues a peer group starting up * after successfully generating hostkeys for each peer. * * @param pg the peer group to continue starting * */ void GNUNET_TESTING_daemons_continue_startup (struct GNUNET_TESTING_PeerGroup *pg) { unsigned int i; #if USE_START_HELPER if ((pg->num_hosts > 0) && (pg->hostkey_data != NULL)) { struct PeerStartHelperContext *helper; pg->starting = pg->num_hosts; for (i = 0; i < pg->num_hosts; i++) { helper = GNUNET_malloc (sizeof (struct PeerStartHelperContext)); helper->pg = pg; helper->host = &pg->hosts[i]; GNUNET_SCHEDULER_add_now (&start_peer_helper, helper); } } else { pg->starting = 0; for (i = 0; i < pg->total; i++) { pg->peers[i].startup_task = GNUNET_SCHEDULER_add_now (&internal_continue_startup, &pg->peers[i].internal_context); } } #else pg->starting = 0; for (i = 0; i < pg->total; i++) { pg->peers[i].startup_task = GNUNET_SCHEDULER_add_now (&internal_continue_startup, &pg->peers[i].internal_context); } #endif } #if USE_START_HELPER static void call_hostkey_callbacks (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct GNUNET_TESTING_PeerGroup *pg = cls; unsigned int i; for (i = 0; i < pg->total; i++) { if (pg->peers[i].internal_context.hostkey_callback != NULL) pg->peers[i].internal_context.hostkey_callback (pg->peers[i]. internal_context.hostkey_cls, &pg->peers[i].daemon->id, pg->peers[i].daemon, NULL); } if (pg->peers[0].internal_context.hostkey_callback == NULL) GNUNET_TESTING_daemons_continue_startup (pg); } #endif /** * Start count gnunet instances with the same set of transports and * applications. The port numbers (any option called "PORT") will be * adjusted to ensure that no two peers running on the same system * have the same port(s) in their respective configurations. * * @param cfg configuration template to use * @param total number of daemons to start * @param max_concurrent_connections for testing, how many peers can * we connect to simultaneously * @param max_concurrent_ssh when starting with ssh, how many ssh * connections will we allow at once (based on remote hosts allowed!) * @param timeout total time allowed for peers to start * @param hostkey_callback function to call on each peers hostkey generation * if NULL, peers will be started by this call, if non-null, * GNUNET_TESTING_daemons_continue_startup must be called after * successful hostkey generation * @param hostkey_cls closure for hostkey callback * @param cb function to call on each daemon that was started * @param cb_cls closure for cb * @param connect_callback function to call each time two hosts are connected * @param connect_callback_cls closure for connect_callback * @param hostnames linked list of host structs to use to start peers on * (NULL to run on localhost only) * * @return NULL on error, otherwise handle to control peer group */ struct GNUNET_TESTING_PeerGroup * GNUNET_TESTING_daemons_start (const struct GNUNET_CONFIGURATION_Handle *cfg, unsigned int total, unsigned int max_concurrent_connections, unsigned int max_concurrent_ssh, struct GNUNET_TIME_Relative timeout, GNUNET_TESTING_NotifyHostkeyCreated hostkey_callback, void *hostkey_cls, GNUNET_TESTING_NotifyDaemonRunning cb, void *cb_cls, GNUNET_TESTING_NotifyConnection connect_callback, void *connect_callback_cls, const struct GNUNET_TESTING_Host *hostnames) { struct GNUNET_TESTING_PeerGroup *pg; const struct GNUNET_TESTING_Host *hostpos; const char *hostname; const char *username; char *baseservicehome; char *newservicehome; char *tmpdir; char *hostkeys_file; char *arg; char *ssh_port_str; struct GNUNET_DISK_FileHandle *fd; struct GNUNET_CONFIGURATION_Handle *pcfg; unsigned int off; struct OutstandingSSH *ssh_entry; unsigned int hostcnt; unsigned int i; uint16_t minport; uint16_t sshport; uint32_t upnum; uint32_t fdnum; uint64_t fs; uint64_t total_hostkeys; struct GNUNET_OS_Process *proc; username = NULL; if (0 == total) { GNUNET_break (0); return NULL; } upnum = 0; fdnum = 0; pg = GNUNET_malloc (sizeof (struct GNUNET_TESTING_PeerGroup)); pg->cfg = cfg; pg->notify_connection = connect_callback; pg->notify_connection_cls = connect_callback_cls; pg->total = total; pg->max_timeout = GNUNET_TIME_relative_to_absolute (timeout); pg->peers = GNUNET_malloc (total * sizeof (struct PeerData)); pg->max_outstanding_connections = max_concurrent_connections; pg->max_concurrent_ssh = max_concurrent_ssh; if (NULL != hostnames) { off = 0; hostpos = hostnames; while (hostpos != NULL) { hostpos = hostpos->next; off++; } pg->hosts = GNUNET_malloc (off * sizeof (struct HostData)); off = 0; hostpos = hostnames; while (hostpos != NULL) { pg->hosts[off].minport = LOW_PORT; pg->hosts[off].hostname = GNUNET_strdup (hostpos->hostname); if (hostpos->username != NULL) pg->hosts[off].username = GNUNET_strdup (hostpos->username); pg->hosts[off].sshport = hostpos->port; hostpos = hostpos->next; off++; } if (off == 0) { pg->hosts = NULL; } hostcnt = off; minport = 0; pg->num_hosts = off; } else { hostcnt = 0; minport = LOW_PORT; } /* Create the servicehome directory for each remote peer */ GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, "PATHS", "SERVICEHOME", &baseservicehome)); for (i = 0; i < pg->num_hosts; i++) { ssh_entry = GNUNET_malloc (sizeof (struct OutstandingSSH)); ssh_entry->hostname = pg->hosts[i].hostname; /* Don't free! */ GNUNET_CONTAINER_DLL_insert (pg->ssh_head, pg->ssh_tail, ssh_entry); GNUNET_asprintf (&tmpdir, "%s/%s", baseservicehome, pg->hosts[i].hostname); if (NULL != pg->hosts[i].username) GNUNET_asprintf (&arg, "%s@%s", pg->hosts[i].username, pg->hosts[i].hostname); else GNUNET_asprintf (&arg, "%s", pg->hosts[i].hostname); if (pg->hosts[i].sshport != 0) { GNUNET_asprintf (&ssh_port_str, "%d", pg->hosts[i].sshport); proc = GNUNET_OS_start_process (GNUNET_NO, NULL, NULL, "ssh", "ssh", "-P", ssh_port_str, #if !DEBUG_TESTING "-q", #endif arg, "mkdir -p", tmpdir, NULL); } else proc = GNUNET_OS_start_process (GNUNET_NO, NULL, NULL, "ssh", "ssh", arg, "mkdir -p", tmpdir, NULL); GNUNET_assert (proc != NULL); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Creating remote dir with command ssh %s %s %s\n", arg, " mkdir -p ", tmpdir); GNUNET_free (tmpdir); GNUNET_free (arg); GNUNET_OS_process_wait (proc); GNUNET_OS_process_close (proc); } GNUNET_free (baseservicehome); baseservicehome = NULL; if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_string (cfg, "TESTING", "HOSTKEYSFILE", &hostkeys_file)) { if (GNUNET_YES != GNUNET_DISK_file_test (hostkeys_file)) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Could not read hostkeys file!\n")); else { /* Check hostkey file size, read entire thing into memory */ fd = GNUNET_DISK_file_open (hostkeys_file, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (NULL == fd) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", hostkeys_file); GNUNET_free (hostkeys_file); for (i = 0; i < pg->num_hosts; i++) { GNUNET_free (pg->hosts[i].hostname); GNUNET_free_non_null (pg->hosts[i].username); } GNUNET_free (pg->peers); GNUNET_free (pg->hosts); GNUNET_free (pg); return NULL; } if (GNUNET_YES != GNUNET_DISK_file_size (hostkeys_file, &fs, GNUNET_YES)) fs = 0; #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found file size %llu for hostkeys\n", fs); #endif if (0 != (fs % HOSTKEYFILESIZE)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "File size %llu seems incorrect for hostkeys...\n", fs); } else { total_hostkeys = fs / HOSTKEYFILESIZE; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Will read %llu hostkeys from file\n", total_hostkeys); pg->hostkey_data = GNUNET_malloc_large (fs); GNUNET_assert (fs == GNUNET_DISK_file_read (fd, pg->hostkey_data, fs)); } GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fd)); } GNUNET_free (hostkeys_file); } for (off = 0; off < total; off++) { if (hostcnt > 0) { hostname = pg->hosts[off % hostcnt].hostname; username = pg->hosts[off % hostcnt].username; sshport = pg->hosts[off % hostcnt].sshport; pcfg = GNUNET_TESTING_create_cfg (cfg, off, &pg->hosts[off % hostcnt].minport, &upnum, hostname, &fdnum); } else { hostname = NULL; username = NULL; sshport = 0; pcfg = GNUNET_TESTING_create_cfg (cfg, off, &minport, &upnum, hostname, &fdnum); } if (NULL == pcfg) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Could not create configuration for peer number %u on `%s'!\n"), off, hostname == NULL ? "localhost" : hostname); continue; } if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_string (pcfg, "PATHS", "SERVICEHOME", &baseservicehome)) { if (hostname != NULL) GNUNET_asprintf (&newservicehome, "%s/%s/%d/", baseservicehome, hostname, off); else GNUNET_asprintf (&newservicehome, "%s/%d/", baseservicehome, off); GNUNET_free (baseservicehome); baseservicehome = NULL; } else { tmpdir = getenv ("TMPDIR"); tmpdir = tmpdir ? tmpdir : "/tmp"; if (hostname != NULL) GNUNET_asprintf (&newservicehome, "%s/%s/%s/%d/", tmpdir, hostname, "gnunet-testing-test-test", off); else GNUNET_asprintf (&newservicehome, "%s/%s/%d/", tmpdir, "gnunet-testing-test-test", off); } GNUNET_CONFIGURATION_set_value_string (pcfg, "PATHS", "SERVICEHOME", newservicehome); GNUNET_free (newservicehome); pg->peers[off].cfg = pcfg; pg->peers[off].pg = pg; pg->peers[off].internal_context.peer = &pg->peers[off]; pg->peers[off].internal_context.timeout = timeout; pg->peers[off].internal_context.hostname = hostname; pg->peers[off].internal_context.username = username; pg->peers[off].internal_context.sshport = sshport; if (pg->hostkey_data != NULL) pg->peers[off].internal_context.hostkey = &pg->hostkey_data[off * HOSTKEYFILESIZE]; pg->peers[off].internal_context.hostkey_callback = hostkey_callback; pg->peers[off].internal_context.hostkey_cls = hostkey_cls; pg->peers[off].internal_context.start_cb = cb; pg->peers[off].internal_context.start_cb_cls = cb_cls; #if !USE_START_HELPER GNUNET_SCHEDULER_add_now (&internal_start, &pg->peers[off].internal_context); #else if ((pg->hostkey_data != NULL) && (hostcnt > 0)) { pg->peers[off].daemon = GNUNET_TESTING_daemon_start (pcfg, timeout, GNUNET_YES, hostname, username, sshport, pg->peers[off].internal_context.hostkey, &internal_hostkey_callback, &pg->peers[off].internal_context, &internal_startup_callback, &pg->peers[off].internal_context); /** * At this point, given that we had a hostkeyfile, * we can call the hostkey callback! * But first, we should copy (rsync) all of the configs * and hostkeys to the remote peers. Then let topology * creation happen, then call the peer start helper processes, * then set pg->whatever_phase for each peer and let them * enter the fsm to get the HELLO's for peers and start connecting. */ } else { GNUNET_SCHEDULER_add_now (&internal_start, &pg->peers[off].internal_context); } #endif } #if USE_START_HELPER /* Now the peergroup has been set up, hostkeys and configs written to files. */ if ((pg->hostkey_data != NULL) && (hostcnt > 0)) { for (off = 0; off < hostcnt; off++) { if (hostcnt > 0) { hostname = pg->hosts[off % hostcnt].hostname; username = pg->hosts[off % hostcnt].username; sshport = pg->hosts[off % hostcnt].sshport; } else { hostname = NULL; username = NULL; sshport = 0; } if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_string (cfg, "PATHS", "SERVICEHOME", &baseservicehome)) { #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "baseservice home is %s\n", baseservicehome); #endif if (hostname != NULL) GNUNET_asprintf (&newservicehome, "%s/%s/", baseservicehome, hostname); else GNUNET_asprintf (&newservicehome, "%s/", baseservicehome); GNUNET_free (baseservicehome); baseservicehome = NULL; } else { tmpdir = getenv ("TMPDIR"); tmpdir = tmpdir ? tmpdir : "/tmp"; if (hostname != NULL) GNUNET_asprintf (&newservicehome, "%s/%s/%s/", tmpdir, hostname, "gnunet-testing-test-test"); else GNUNET_asprintf (&newservicehome, "%s/%s/", tmpdir, "gnunet-testing-test-test", off); } if (NULL != username) GNUNET_asprintf (&arg, "%s@%s:%s", username, pg->hosts[off].hostname, newservicehome); else GNUNET_asprintf (&arg, "%s:%s", pg->hosts[off].hostname, newservicehome); /* FIXME: Doesn't support ssh_port option! */ proc = GNUNET_OS_start_process (GNUNET_NO, NULL, NULL, "rsync", "rsync", "-r", newservicehome, arg, NULL); #if DEBUG_TESTING GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "copying directory with command rsync -r %s %s\n", newservicehome, arg); #endif GNUNET_free (newservicehome); GNUNET_free (arg); if (NULL == proc) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Could not start `%s' process to copy configuration directory.\n"), "scp"); GNUNET_assert (0); } GNUNET_OS_process_wait (proc); GNUNET_OS_process_close (proc); } /* Now all the configuration files and hostkeys are copied to the remote host. Call the hostkey callback for each peer! */ GNUNET_SCHEDULER_add_now (&call_hostkey_callbacks, pg); } #endif return pg; } /* * Get a daemon by number, so callers don't have to do nasty * offsetting operation. */ struct GNUNET_TESTING_Daemon * GNUNET_TESTING_daemon_get (struct GNUNET_TESTING_PeerGroup *pg, unsigned int position) { if (position < pg->total) return pg->peers[position].daemon; return NULL; } /* * Get a daemon by peer identity, so callers can * retrieve the daemon without knowing it's offset. * * @param pg the peer group to retrieve the daemon from * @param peer_id the peer identity of the daemon to retrieve * * @return the daemon on success, or NULL if no such peer identity is found */ struct GNUNET_TESTING_Daemon * GNUNET_TESTING_daemon_get_by_id (struct GNUNET_TESTING_PeerGroup *pg, const struct GNUNET_PeerIdentity *peer_id) { unsigned int i; for (i = 0; i < pg->total; i++) { if (0 == memcmp (&pg->peers[i].daemon->id, peer_id, sizeof (struct GNUNET_PeerIdentity))) return pg->peers[i].daemon; } return NULL; } /** * Prototype of a function that will be called when a * particular operation was completed the testing library. * * @param cls closure (a struct RestartContext) * @param id id of the peer that was restarted * @param cfg handle to the configuration of the peer * @param d handle to the daemon that was restarted * @param emsg NULL on success */ static void restart_callback (void *cls, const struct GNUNET_PeerIdentity *id, const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_TESTING_Daemon *d, const char *emsg) { struct RestartContext *restart_context = cls; if (emsg == NULL) { restart_context->peers_restarted++; } else { restart_context->peers_restart_failed++; } if (restart_context->peers_restarted == restart_context->peer_group->total) { restart_context->callback (restart_context->callback_cls, NULL); GNUNET_free (restart_context); } else if (restart_context->peers_restart_failed + restart_context->peers_restarted == restart_context->peer_group->total) { restart_context->callback (restart_context->callback_cls, "Failed to restart peers!"); GNUNET_free (restart_context); } } /** * Callback for informing us about a successful * or unsuccessful churn stop call. * * @param cls a ChurnContext * @param emsg NULL on success, non-NULL on failure * */ static void churn_stop_callback (void *cls, const char *emsg) { struct ShutdownContext *shutdown_ctx = cls; struct ChurnContext *churn_ctx = shutdown_ctx->cb_cls; unsigned int total_left; char *error_message; error_message = NULL; shutdown_ctx->outstanding--; if (emsg != NULL) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Churn stop callback failed with error `%s'\n", emsg); churn_ctx->num_failed_stop++; } else { churn_ctx->num_to_stop--; } total_left = (churn_ctx->num_to_stop - churn_ctx->num_failed_stop) + (churn_ctx->num_to_start - churn_ctx->num_failed_start); if (total_left == 0) { if ((churn_ctx->num_failed_stop > 0) || (churn_ctx->num_failed_start > 0)) { GNUNET_asprintf (&error_message, "Churn didn't complete successfully, %u peers failed to start %u peers failed to be stopped!", churn_ctx->num_failed_start, churn_ctx->num_failed_stop); } churn_ctx->cb (churn_ctx->cb_cls, error_message); GNUNET_free_non_null (error_message); GNUNET_free (churn_ctx); GNUNET_free (shutdown_ctx); } } /** * Count the number of running peers. * * @param pg handle for the peer group * * @return the number of currently running peers in the peer group */ unsigned int GNUNET_TESTING_daemons_running (struct GNUNET_TESTING_PeerGroup *pg) { unsigned int i; unsigned int running = 0; for (i = 0; i < pg->total; i++) { if (pg->peers[i].daemon->running == GNUNET_YES) { GNUNET_assert (running != -1); running++; } } return running; } /** * Task to rate limit the number of outstanding peer shutdown * requests. This is necessary for making sure we don't do * too many ssh connections at once, but is generally nicer * to any system as well (graduated task starts, as opposed * to calling gnunet-arm N times all at once). */ static void schedule_churn_shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct PeerShutdownContext *peer_shutdown_ctx = cls; struct ShutdownContext *shutdown_ctx; struct ChurnContext *churn_ctx; GNUNET_assert (peer_shutdown_ctx != NULL); shutdown_ctx = peer_shutdown_ctx->shutdown_ctx; GNUNET_assert (shutdown_ctx != NULL); churn_ctx = (struct ChurnContext *) shutdown_ctx->cb_cls; if (shutdown_ctx->outstanding > churn_ctx->pg->max_concurrent_ssh) GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_churn_shutdown_task, peer_shutdown_ctx); else { shutdown_ctx->outstanding++; if (churn_ctx->service != NULL) GNUNET_TESTING_daemon_stop_service (peer_shutdown_ctx->daemon, churn_ctx->service, shutdown_ctx->timeout, shutdown_ctx->cb, shutdown_ctx); else GNUNET_TESTING_daemon_stop (peer_shutdown_ctx->daemon, shutdown_ctx->timeout, shutdown_ctx->cb, shutdown_ctx, GNUNET_NO, GNUNET_YES); GNUNET_free (peer_shutdown_ctx); } } /** * Simulate churn by stopping some peers (and possibly * re-starting others if churn is called multiple times). This * function can only be used to create leave-join churn (peers "never" * leave for good). First "voff" random peers that are currently * online will be taken offline; then "von" random peers that are then * offline will be put back online. No notifications will be * generated for any of these operations except for the callback upon * completion. * * @param pg handle for the peer group * @param service the service to churn off/on, NULL to churn peer * @param voff number of peers that should go offline * @param von number of peers that should come back online; * must be zero on first call (since "testbed_start" * always starts all of the peers) * @param timeout how long to wait for operations to finish before * giving up * @param cb function to call at the end * @param cb_cls closure for cb */ void GNUNET_TESTING_daemons_churn (struct GNUNET_TESTING_PeerGroup *pg, char *service, unsigned int voff, unsigned int von, struct GNUNET_TIME_Relative timeout, GNUNET_TESTING_NotifyCompletion cb, void *cb_cls) { struct ChurnContext *churn_ctx; struct ShutdownContext *shutdown_ctx; struct PeerShutdownContext *peer_shutdown_ctx; struct PeerRestartContext *peer_restart_ctx; struct ChurnRestartContext *churn_startup_ctx; unsigned int running; unsigned int stopped; unsigned int total_running; unsigned int total_stopped; unsigned int i; unsigned int *running_arr; unsigned int *stopped_arr; unsigned int *running_permute; unsigned int *stopped_permute; char *pos; shutdown_ctx = NULL; peer_shutdown_ctx = NULL; peer_restart_ctx = NULL; churn_startup_ctx = NULL; running = 0; stopped = 0; if ((von == 0) && (voff == 0)) /* No peers at all? */ { cb (cb_cls, NULL); return; } for (i = 0; i < pg->total; i++) { if (service == NULL) { if (pg->peers[i].daemon->running == GNUNET_YES) { GNUNET_assert (running != -1); running++; } else { GNUNET_assert (stopped != -1); stopped++; } } else { /* FIXME: make churned services a list! */ pos = pg->peers[i].daemon->churned_services; /* FIXME: while (pos != NULL) */ if (pos != NULL) { #if FIXME if (0 == strcasecmp (pos, service)) { break; } #endif GNUNET_assert (stopped != -1); stopped++; /* FIXME: pos = pos->next; */ } if (pos == NULL) { GNUNET_assert (running != -1); running++; } } } if (voff > running) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Trying to stop more peers (%d) than are currently running (%d)!\n", voff, running); cb (cb_cls, "Trying to stop more peers than are currently running!"); return; } if (von > stopped) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Trying to start more peers (%d) than are currently stopped (%d)!\n", von, stopped); cb (cb_cls, "Trying to start more peers than are currently stopped!"); return; } churn_ctx = GNUNET_malloc (sizeof (struct ChurnContext)); if (service != NULL) churn_ctx->service = GNUNET_strdup (service); running_arr = NULL; if (running > 0) running_arr = GNUNET_malloc (running * sizeof (unsigned int)); stopped_arr = NULL; if (stopped > 0) stopped_arr = GNUNET_malloc (stopped * sizeof (unsigned int)); running_permute = NULL; stopped_permute = NULL; if (running > 0) running_permute = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK, running); if (stopped > 0) stopped_permute = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK, stopped); total_running = running; total_stopped = stopped; running = 0; stopped = 0; churn_ctx->num_to_start = von; churn_ctx->num_to_stop = voff; churn_ctx->cb = cb; churn_ctx->cb_cls = cb_cls; churn_ctx->pg = pg; for (i = 0; i < pg->total; i++) { if (service == NULL) { if (pg->peers[i].daemon->running == GNUNET_YES) { GNUNET_assert ((running_arr != NULL) && (total_running > running)); running_arr[running] = i; running++; } else { GNUNET_assert ((stopped_arr != NULL) && (total_stopped > stopped)); stopped_arr[stopped] = i; stopped++; } } else { /* FIXME: make churned services a list! */ pos = pg->peers[i].daemon->churned_services; /* FIXME: while (pos != NULL) */ if (pos != NULL) { GNUNET_assert ((stopped_arr != NULL) && (total_stopped > stopped)); stopped_arr[stopped] = i; stopped++; /* FIXME: pos = pos->next; */ } if (pos == NULL) { GNUNET_assert ((running_arr != NULL) && (total_running > running)); running_arr[running] = i; running++; } } } GNUNET_assert (running >= voff); if (voff > 0) { shutdown_ctx = GNUNET_malloc (sizeof (struct ShutdownContext)); shutdown_ctx->cb = &churn_stop_callback; shutdown_ctx->cb_cls = churn_ctx; shutdown_ctx->total_peers = voff; shutdown_ctx->timeout = timeout; } for (i = 0; i < voff; i++) { #if DEBUG_CHURN GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Stopping peer %d!\n", running_arr[running_permute[i]]); #endif GNUNET_assert (running_arr != NULL); peer_shutdown_ctx = GNUNET_malloc (sizeof (struct PeerShutdownContext)); peer_shutdown_ctx->daemon = pg->peers[running_arr[running_permute[i]]].daemon; peer_shutdown_ctx->shutdown_ctx = shutdown_ctx; GNUNET_SCHEDULER_add_now (&schedule_churn_shutdown_task, peer_shutdown_ctx); } GNUNET_assert (stopped >= von); if (von > 0) { churn_startup_ctx = GNUNET_malloc (sizeof (struct ChurnRestartContext)); churn_startup_ctx->churn_ctx = churn_ctx; churn_startup_ctx->timeout = timeout; churn_startup_ctx->pg = pg; } for (i = 0; i < von; i++) { #if DEBUG_CHURN GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting up peer %d!\n", stopped_arr[stopped_permute[i]]); #endif GNUNET_assert (stopped_arr != NULL); peer_restart_ctx = GNUNET_malloc (sizeof (struct PeerRestartContext)); peer_restart_ctx->churn_restart_ctx = churn_startup_ctx; peer_restart_ctx->daemon = pg->peers[stopped_arr[stopped_permute[i]]].daemon; GNUNET_SCHEDULER_add_now (&schedule_churn_restart, peer_restart_ctx); } GNUNET_free_non_null (running_arr); GNUNET_free_non_null (stopped_arr); GNUNET_free_non_null (running_permute); GNUNET_free_non_null (stopped_permute); } /* * Start a given service for each of the peers in the peer group. * * @param pg handle for the peer group * @param service the service to start * @param timeout how long to wait for operations to finish before * giving up * @param cb function to call once finished * @param cb_cls closure for cb * */ void GNUNET_TESTING_daemons_start_service (struct GNUNET_TESTING_PeerGroup *pg, char *service, struct GNUNET_TIME_Relative timeout, GNUNET_TESTING_NotifyCompletion cb, void *cb_cls) { struct ServiceStartContext *start_ctx; struct PeerServiceStartContext *peer_start_ctx; unsigned int i; GNUNET_assert (service != NULL); start_ctx = GNUNET_malloc (sizeof (struct ServiceStartContext)); start_ctx->pg = pg; start_ctx->remaining = pg->total; start_ctx->cb = cb; start_ctx->cb_cls = cb_cls; start_ctx->service = GNUNET_strdup (service); start_ctx->timeout = timeout; for (i = 0; i < pg->total; i++) { #if DEBUG_START GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting up service %s on peer %d!\n", service, stopped_arr[stopped_permute[i]]); #endif peer_start_ctx = GNUNET_malloc (sizeof (struct PeerServiceStartContext)); peer_start_ctx->start_ctx = start_ctx; peer_start_ctx->daemon = pg->peers[i].daemon; GNUNET_SCHEDULER_add_now (&schedule_service_start, peer_start_ctx); } } /** * Restart all peers in the given group. * * @param pg the handle to the peer group * @param callback function to call on completion (or failure) * @param callback_cls closure for the callback function */ void GNUNET_TESTING_daemons_restart (struct GNUNET_TESTING_PeerGroup *pg, GNUNET_TESTING_NotifyCompletion callback, void *callback_cls) { struct RestartContext *restart_context; unsigned int off; if (pg->total > 0) { restart_context = GNUNET_malloc (sizeof (struct RestartContext)); restart_context->peer_group = pg; restart_context->peers_restarted = 0; restart_context->callback = callback; restart_context->callback_cls = callback_cls; for (off = 0; off < pg->total; off++) { GNUNET_TESTING_daemon_restart (pg->peers[off].daemon, &restart_callback, restart_context); } } } /** * Start or stop an individual peer from the given group. * * @param pg handle to the peer group * @param offset which peer to start or stop * @param desired_status GNUNET_YES to have it running, GNUNET_NO to stop it * @param timeout how long to wait for shutdown * @param cb function to call at the end * @param cb_cls closure for cb */ void GNUNET_TESTING_daemons_vary (struct GNUNET_TESTING_PeerGroup *pg, unsigned int offset, int desired_status, struct GNUNET_TIME_Relative timeout, GNUNET_TESTING_NotifyCompletion cb, void *cb_cls) { struct ShutdownContext *shutdown_ctx; struct ChurnRestartContext *startup_ctx; struct ChurnContext *churn_ctx; if (GNUNET_NO == desired_status) { if (NULL != pg->peers[offset].daemon) { shutdown_ctx = GNUNET_malloc (sizeof (struct ShutdownContext)); churn_ctx = GNUNET_malloc (sizeof (struct ChurnContext)); churn_ctx->num_to_start = 0; churn_ctx->num_to_stop = 1; churn_ctx->cb = cb; churn_ctx->cb_cls = cb_cls; shutdown_ctx->cb_cls = churn_ctx; GNUNET_TESTING_daemon_stop (pg->peers[offset].daemon, timeout, &churn_stop_callback, shutdown_ctx, GNUNET_NO, GNUNET_YES); } } else if (GNUNET_YES == desired_status) { if (NULL == pg->peers[offset].daemon) { startup_ctx = GNUNET_malloc (sizeof (struct ChurnRestartContext)); churn_ctx = GNUNET_malloc (sizeof (struct ChurnContext)); churn_ctx->num_to_start = 1; churn_ctx->num_to_stop = 0; churn_ctx->cb = cb; churn_ctx->cb_cls = cb_cls; startup_ctx->churn_ctx = churn_ctx; GNUNET_TESTING_daemon_start_stopped (pg->peers[offset].daemon, timeout, &churn_start_callback, startup_ctx); } } else GNUNET_break (0); } /** * Callback for shutting down peers in a peer group. * * @param cls closure (struct ShutdownContext) * @param emsg NULL on success */ static void internal_shutdown_callback (void *cls, const char *emsg) { struct PeerShutdownContext *peer_shutdown_ctx = cls; struct ShutdownContext *shutdown_ctx = peer_shutdown_ctx->shutdown_ctx; unsigned int off; int i; struct OutstandingSSH *ssh_pos; shutdown_ctx->outstanding--; if (peer_shutdown_ctx->daemon->hostname != NULL) decrement_outstanding_at_host (peer_shutdown_ctx->daemon->hostname, shutdown_ctx->pg); if (emsg == NULL) { shutdown_ctx->peers_down++; } else { #if VERBOSE_TESTING GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "internal_shutdown_callback", "Failed to stop a peer: %s\n", emsg); #endif shutdown_ctx->peers_failed++; } if ((shutdown_ctx->cb != NULL) && (shutdown_ctx->peers_down + shutdown_ctx->peers_failed == shutdown_ctx->total_peers)) { if (shutdown_ctx->peers_failed > 0) shutdown_ctx->cb (shutdown_ctx->cb_cls, "Not all peers successfully shut down!"); else shutdown_ctx->cb (shutdown_ctx->cb_cls, NULL); for (i = 0; i < shutdown_ctx->pg->total; i++) { if (shutdown_ctx->pg->peers[i].startup_task != GNUNET_SCHEDULER_NO_TASK) GNUNET_SCHEDULER_cancel (shutdown_ctx->pg->peers[i].startup_task); } GNUNET_free (shutdown_ctx->pg->peers); GNUNET_free_non_null (shutdown_ctx->pg->hostkey_data); for (off = 0; off < shutdown_ctx->pg->num_hosts; off++) { GNUNET_free (shutdown_ctx->pg->hosts[off].hostname); GNUNET_free_non_null (shutdown_ctx->pg->hosts[off].username); } GNUNET_free_non_null (shutdown_ctx->pg->hosts); while (NULL != (ssh_pos = shutdown_ctx->pg->ssh_head)) { GNUNET_CONTAINER_DLL_remove (shutdown_ctx->pg->ssh_head, shutdown_ctx->pg->ssh_tail, ssh_pos); GNUNET_free (ssh_pos); } GNUNET_free (shutdown_ctx->pg); GNUNET_free (shutdown_ctx); } GNUNET_free (peer_shutdown_ctx); } /** * Task to rate limit the number of outstanding peer shutdown * requests. This is necessary for making sure we don't do * too many ssh connections at once, but is generally nicer * to any system as well (graduated task starts, as opposed * to calling gnunet-arm N times all at once). */ static void schedule_shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct PeerShutdownContext *peer_shutdown_ctx = cls; struct ShutdownContext *shutdown_ctx; GNUNET_assert (peer_shutdown_ctx != NULL); shutdown_ctx = peer_shutdown_ctx->shutdown_ctx; GNUNET_assert (shutdown_ctx != NULL); if ((shutdown_ctx->outstanding < shutdown_ctx->pg->max_concurrent_ssh) || ((peer_shutdown_ctx->daemon->hostname != NULL) && (count_outstanding_at_host (peer_shutdown_ctx->daemon->hostname, shutdown_ctx->pg) < shutdown_ctx->pg->max_concurrent_ssh))) { if (peer_shutdown_ctx->daemon->hostname != NULL) increment_outstanding_at_host (peer_shutdown_ctx->daemon->hostname, shutdown_ctx->pg); shutdown_ctx->outstanding++; GNUNET_TESTING_daemon_stop (peer_shutdown_ctx->daemon, shutdown_ctx->timeout, &internal_shutdown_callback, peer_shutdown_ctx, shutdown_ctx->delete_files, GNUNET_NO); } else GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &schedule_shutdown_task, peer_shutdown_ctx); } /** * Read a testing hosts file based on a configuration. * Returns a DLL of hosts (caller must free!) on success * or NULL on failure. * * @param cfg a configuration with a testing section * * @return DLL of hosts on success, NULL on failure */ struct GNUNET_TESTING_Host * GNUNET_TESTING_hosts_load (const struct GNUNET_CONFIGURATION_Handle *cfg) { struct GNUNET_TESTING_Host *hosts; struct GNUNET_TESTING_Host *temphost; char *data; char *buf; char *hostfile; struct stat frstat; int count; int ret; /* Check for a hostfile containing user@host:port triples */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "testing", "hostfile", &hostfile)) return NULL; hosts = NULL; temphost = NULL; data = NULL; if (hostfile != NULL) { if (GNUNET_OK != GNUNET_DISK_file_test (hostfile)) GNUNET_DISK_fn_write (hostfile, NULL, 0, GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE); if ((0 != STAT (hostfile, &frstat)) || (frstat.st_size == 0)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not open file specified for host list, ending test!"); GNUNET_free (hostfile); return NULL; } data = GNUNET_malloc_large (frstat.st_size); GNUNET_assert (data != NULL); if (frstat.st_size != GNUNET_DISK_fn_read (hostfile, data, frstat.st_size)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not read file %s specified for host list, ending test!", hostfile); GNUNET_free (hostfile); GNUNET_free (data); return NULL; } GNUNET_free_non_null (hostfile); buf = data; count = 0; while (count < frstat.st_size - 1) { count++; if (((data[count] == '\n')) && (buf != &data[count])) { data[count] = '\0'; temphost = GNUNET_malloc (sizeof (struct GNUNET_TESTING_Host)); ret = sscanf (buf, "%a[a-zA-Z0-9_]@%a[a-zA-Z0-9.]:%hd", &temphost->username, &temphost->hostname, &temphost->port); if (3 == ret) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Successfully read host %s, port %d and user %s from file\n", temphost->hostname, temphost->port, temphost->username); } else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Error reading line `%s' in hostfile\n", buf); GNUNET_free (temphost); buf = &data[count + 1]; continue; } temphost->next = hosts; hosts = temphost; buf = &data[count + 1]; } else if ((data[count] == '\n') || (data[count] == '\0')) buf = &data[count + 1]; } } GNUNET_free_non_null (data); return hosts; } /** * Shutdown all peers started in the given group. * * @param pg handle to the peer group * @param timeout how long to wait for shutdown * @param cb callback to notify upon success or failure * @param cb_cls closure for cb */ void GNUNET_TESTING_daemons_stop (struct GNUNET_TESTING_PeerGroup *pg, struct GNUNET_TIME_Relative timeout, GNUNET_TESTING_NotifyCompletion cb, void *cb_cls) { unsigned int off; struct ShutdownContext *shutdown_ctx; struct PeerShutdownContext *peer_shutdown_ctx; #if OLD struct PeerConnection *conn_iter; struct PeerConnection *temp_conn; #endif struct ConnectContext *cc; GNUNET_assert (pg->total > 0); while (NULL != (cc = pg->cc_head)) { GNUNET_CONTAINER_DLL_remove (pg->cc_head, pg->cc_tail, cc); if (GNUNET_SCHEDULER_NO_TASK != cc->task) GNUNET_SCHEDULER_cancel (cc->task); if (NULL != cc->cc) GNUNET_TESTING_daemons_connect_cancel (cc->cc); GNUNET_free (cc); } shutdown_ctx = GNUNET_malloc (sizeof (struct ShutdownContext)); shutdown_ctx->delete_files = GNUNET_CONFIGURATION_get_value_yesno (pg->cfg, "TESTING", "DELETE_FILES"); shutdown_ctx->cb = cb; shutdown_ctx->cb_cls = cb_cls; shutdown_ctx->total_peers = pg->total; shutdown_ctx->timeout = timeout; shutdown_ctx->pg = pg; for (off = 0; off < pg->total; off++) { GNUNET_assert (NULL != pg->peers[off].daemon); peer_shutdown_ctx = GNUNET_malloc (sizeof (struct PeerShutdownContext)); peer_shutdown_ctx->daemon = pg->peers[off].daemon; peer_shutdown_ctx->shutdown_ctx = shutdown_ctx; GNUNET_SCHEDULER_add_now (&schedule_shutdown_task, peer_shutdown_ctx); if (NULL != pg->peers[off].cfg) { GNUNET_CONFIGURATION_destroy (pg->peers[off].cfg); pg->peers[off].cfg = NULL; } #if OLD // FIXME Do DLL remove for all pg->peers[off].LIST conn_iter = pg->peers[off].allowed_peers_head; while (conn_iter != NULL) { temp_conn = conn_iter->next; GNUNET_free (conn_iter); conn_iter = temp_conn; } pg->peers[off].allowed_peers_head = NULL; conn_iter = pg->peers[off].connect_peers_head; while (conn_iter != NULL) { temp_conn = conn_iter->next; GNUNET_free (conn_iter); conn_iter = temp_conn; } pg->peers[off].connect_peers_head = NULL; conn_iter = pg->peers[off].blacklisted_peers_head; while (conn_iter != NULL) { temp_conn = conn_iter->next; GNUNET_free (conn_iter); conn_iter = temp_conn; } pg->peers[off].blacklisted_peers_head = NULL; conn_iter = pg->peers[off].connect_peers_working_set_head; while (conn_iter != NULL) { temp_conn = conn_iter->next; GNUNET_free (conn_iter); conn_iter = temp_conn; } pg->peers[off].connect_peers_working_set_head = NULL; #else if (pg->peers[off].allowed_peers != NULL) GNUNET_CONTAINER_multihashmap_destroy (pg->peers[off].allowed_peers); if (pg->peers[off].connect_peers != NULL) GNUNET_CONTAINER_multihashmap_destroy (pg->peers[off].connect_peers); if (pg->peers[off].blacklisted_peers != NULL) GNUNET_CONTAINER_multihashmap_destroy (pg->peers[off].blacklisted_peers); #endif } } /* end of testing_group.c */