commit 1b0f53e7dc73acda215abb20b01bea19a7016551
parent f43c9b7406ef68098a01d5fdfd4cb7a8932fdf09
Author: Jacki <jacki@thejackimonster.de>
Date: Mon, 3 Feb 2025 05:26:42 +0100
Implement ping tool to measure latency between message exchange
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
3 files changed, 611 insertions(+), 4 deletions(-)
diff --git a/tools/gnunet_messenger_ping.c b/tools/gnunet_messenger_ping.c
@@ -0,0 +1,596 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2025 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file gnunet_messenger_ping.c
+ */
+
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_identity_service.h>
+#include <gnunet/gnunet_messenger_service.h>
+#include <gnunet/gnunet_scheduler_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+struct GNUNET_MESSENGER_PingTool
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+ struct GNUNET_IDENTITY_EgoLookup *lookup;
+ struct GNUNET_MESSENGER_Handle *handle;
+ struct GNUNET_SCHEDULER_Task *task;
+
+ struct GNUNET_CONTAINER_MultiHashMap *map;
+ struct GNUNET_CONTAINER_MultiHashMap *ping_map;
+
+ char *ego_name;
+ char *room_name;
+ uint count;
+ int public_room;
+ int auto_pong;
+
+ bool quit;
+};
+
+struct GNUNET_MESSENGER_Ping
+{
+ struct GNUNET_TIME_Absolute ping_time;
+ const struct GNUNET_MESSENGER_Contact *sender;
+
+ struct GNUNET_CONTAINER_MultiShortmap *pong_map;
+
+ size_t pong_missing;
+ size_t traffic;
+};
+
+static const struct GNUNET_ShortHashCode*
+hash_contact (const struct GNUNET_MESSENGER_Contact *contact)
+{
+ static struct GNUNET_ShortHashCode hash;
+ memset(&hash, 0, sizeof (hash));
+
+ size_t id = GNUNET_MESSENGER_contact_get_id(contact);
+ GNUNET_memcpy(&hash, &id, sizeof (id));
+
+ return &hash;
+}
+
+static void
+idle (void *cls)
+{
+ struct GNUNET_MESSENGER_PingTool *tool = cls;
+
+ if ((tool->auto_pong) && (!(tool->quit)))
+ {
+ tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
+ GNUNET_TIME_relative_get_second_(),
+ GNUNET_SCHEDULER_PRIORITY_IDLE,
+ idle,
+ tool
+ );
+ return;
+ }
+
+ tool->task = NULL;
+ tool->quit = true;
+
+ if (tool->handle)
+ GNUNET_MESSENGER_disconnect(tool->handle);
+
+ if (tool->lookup)
+ GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup);
+}
+
+static enum GNUNET_GenericReturnValue
+member_callback (void *cls,
+ struct GNUNET_MESSENGER_Room *room,
+ const struct GNUNET_MESSENGER_Contact *contact)
+{
+ struct GNUNET_MESSENGER_Ping *ping = cls;
+
+ if (contact == ping->sender)
+ return GNUNET_YES;
+
+ GNUNET_CONTAINER_multishortmap_put(ping->pong_map, hash_contact (contact), NULL,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
+
+ return GNUNET_YES;
+}
+
+static bool
+is_hash_following (struct GNUNET_MESSENGER_PingTool *tool,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_HashCode *prev);
+
+struct GNUNET_MESSENGER_HashCheck
+{
+ struct GNUNET_MESSENGER_PingTool *tool;
+ const struct GNUNET_HashCode *hash;
+ bool result;
+};
+
+static enum GNUNET_GenericReturnValue
+multiple_iterator_callback (void *cls,
+ const struct GNUNET_HashCode *hash,
+ void *value)
+{
+ struct GNUNET_MESSENGER_HashCheck *check = cls;
+ const struct GNUNET_HashCode *prev = value;
+
+ if (is_hash_following (check->tool, prev, check->hash))
+ {
+ check->result = true;
+ return GNUNET_NO;
+ }
+
+ return GNUNET_YES;
+}
+
+static bool
+is_hash_following (struct GNUNET_MESSENGER_PingTool *tool,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_HashCode *prev)
+{
+ if (0 == GNUNET_CRYPTO_hash_cmp(hash, prev))
+ return true;
+
+ struct GNUNET_MESSENGER_HashCheck check;
+ check.tool = tool;
+ check.hash = prev;
+ check.result = false;
+
+ GNUNET_CONTAINER_multihashmap_get_multiple(tool->map, hash,
+ multiple_iterator_callback,
+ &check);
+
+ return check.result;
+}
+
+static void
+send_ping (struct GNUNET_MESSENGER_PingTool *tool,
+ struct GNUNET_MESSENGER_Room *room)
+{
+ struct GNUNET_MESSENGER_Message message;
+ message.header.kind = GNUNET_MESSENGER_KIND_TEXT;
+ message.body.text.text = NULL;
+
+ GNUNET_MESSENGER_send_message(room, &message, NULL);
+
+ if (tool->count)
+ tool->count--;
+}
+
+static void
+send_pong (struct GNUNET_MESSENGER_PingTool *tool,
+ struct GNUNET_MESSENGER_Room *room,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_TIME_Absolute timestamp)
+{
+ struct GNUNET_MESSENGER_Message message;
+ message.header.kind = GNUNET_MESSENGER_KIND_TAG;
+ message.body.tag.tag = NULL;
+
+ GNUNET_memcpy(&(message.body.tag.hash), hash, sizeof(*hash));
+
+ const struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
+ timestamp, GNUNET_TIME_absolute_get());
+
+ printf("%s as response to %s from: time=%.3f ms\n",
+ GNUNET_MESSENGER_name_of_kind(message.header.kind),
+ GNUNET_h2s(hash),
+ ((float) difference.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
+
+ GNUNET_MESSENGER_send_message(room, &message, NULL);
+
+ if (tool->count)
+ tool->count--;
+
+ if (!(tool->count))
+ tool->quit = true;
+}
+
+static void
+finish_ping (struct GNUNET_MESSENGER_PingTool *tool,
+ struct GNUNET_MESSENGER_Ping *ping,
+ struct GNUNET_MESSENGER_Room *room,
+ const struct GNUNET_HashCode *hash)
+{
+ const size_t recipients = GNUNET_CONTAINER_multishortmap_size(ping->pong_map);
+ const size_t loss_rate = recipients? 100 * ping->pong_missing / recipients : 100;
+ const struct GNUNET_TIME_Relative delta = GNUNET_TIME_absolute_get_difference(
+ ping->ping_time, GNUNET_TIME_absolute_get());
+
+ printf("--- %s ping statistics ---\n", GNUNET_h2s(hash));
+
+ struct GNUNET_TIME_Relative min = GNUNET_TIME_relative_get_forever_();
+ struct GNUNET_TIME_Relative avg = GNUNET_TIME_relative_get_zero_();
+ struct GNUNET_TIME_Relative max = GNUNET_TIME_relative_get_zero_();
+ struct GNUNET_TIME_Relative mdev = GNUNET_TIME_relative_get_zero_();
+
+ struct GNUNET_CONTAINER_MultiShortmapIterator *iter;
+ const void *value;
+
+ iter = GNUNET_CONTAINER_multishortmap_iterator_create(ping->pong_map);
+
+ while (GNUNET_NO != GNUNET_CONTAINER_multishortmap_iterator_next(iter, NULL, &value))
+ {
+ if (!value)
+ continue;
+
+ const struct GNUNET_TIME_Absolute *time = value;
+ struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
+ ping->ping_time, *time);
+
+ if (GNUNET_TIME_relative_cmp(difference, <, min))
+ min = difference;
+ if (GNUNET_TIME_relative_cmp(difference, >, max))
+ max = difference;
+
+ avg = GNUNET_TIME_relative_add(avg, difference);
+ }
+
+ GNUNET_CONTAINER_multishortmap_iterator_destroy(iter);
+
+ if (recipients > ping->pong_missing)
+ avg = GNUNET_TIME_relative_divide(avg, recipients - ping->pong_missing);
+
+ iter = GNUNET_CONTAINER_multishortmap_iterator_create(ping->pong_map);
+
+ while (GNUNET_NO != GNUNET_CONTAINER_multishortmap_iterator_next(iter, NULL, &value))
+ {
+ if (!value)
+ continue;
+
+ const struct GNUNET_TIME_Absolute *time = value;
+ struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
+ ping->ping_time, *time);
+
+ difference = GNUNET_TIME_relative_subtract(difference, avg);
+ difference = GNUNET_TIME_relative_saturating_multiply(difference,
+ difference.rel_value_us);
+
+ mdev = GNUNET_TIME_relative_add(mdev, difference);
+ }
+
+ GNUNET_CONTAINER_multishortmap_iterator_destroy(iter);
+
+ if (recipients > ping->pong_missing)
+ mdev = GNUNET_TIME_relative_divide(mdev, recipients - ping->pong_missing);
+
+ mdev.rel_value_us = (uint64_t) sqrt(mdev.rel_value_us);
+
+ printf("%lu messages exchanged, %lu recipients, %lu%% message loss, time %.3fms\n",
+ ping->traffic, recipients, loss_rate, ((float) delta.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
+
+ if (recipients > 0)
+ printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n",
+ ((float) min.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us,
+ ((float) avg.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us,
+ ((float) max.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us,
+ ((float) mdev.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
+
+ if (!(tool->quit))
+ {
+ if (tool->count)
+ {
+ printf("\n");
+
+ send_ping(tool, room);
+ }
+ else
+ tool->quit = true;
+ }
+}
+
+static void
+message_callback (void *cls,
+ struct GNUNET_MESSENGER_Room *room,
+ const struct GNUNET_MESSENGER_Contact *sender,
+ const struct GNUNET_MESSENGER_Contact *recipient,
+ const struct GNUNET_MESSENGER_Message *message,
+ const struct GNUNET_HashCode *hash,
+ enum GNUNET_MESSENGER_MessageFlags flags)
+{
+ struct GNUNET_MESSENGER_PingTool *tool = cls;
+
+ if (tool->auto_pong)
+ {
+ if ((!(GNUNET_MESSENGER_FLAG_SENT & flags)) &&
+ (GNUNET_MESSENGER_KIND_TEXT == message->header.kind))
+ send_pong(tool, room, hash, GNUNET_TIME_absolute_ntoh(message->header.timestamp));
+
+ goto skip_ping;
+ }
+
+ {
+ struct GNUNET_HashCode *copy = GNUNET_new(struct GNUNET_HashCode);
+ GNUNET_memcpy(copy, &(message->header.previous), sizeof (*copy));
+
+ GNUNET_CONTAINER_multihashmap_put(tool->map, hash, copy,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ }
+
+ if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind)
+ {
+ struct GNUNET_HashCode *copy = GNUNET_new(struct GNUNET_HashCode);
+ GNUNET_memcpy(copy, &(message->body.merge.previous), sizeof (*copy));
+
+ GNUNET_CONTAINER_multihashmap_put(tool->map, hash, copy,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ }
+
+ if (GNUNET_MESSENGER_FLAG_SENT & flags)
+ {
+ switch (message->header.kind)
+ {
+ case GNUNET_MESSENGER_KIND_JOIN:
+ {
+ send_ping(tool, room);
+ break;
+ }
+ case GNUNET_MESSENGER_KIND_TEXT:
+ {
+ struct GNUNET_MESSENGER_Ping *ping = GNUNET_new(struct GNUNET_MESSENGER_Ping);
+
+ ping->ping_time = GNUNET_TIME_absolute_ntoh(message->header.timestamp);
+ ping->sender = sender;
+
+ ping->pong_map = GNUNET_CONTAINER_multishortmap_create(8, GNUNET_NO);
+
+ GNUNET_MESSENGER_iterate_members(room, member_callback, ping);
+
+ ping->pong_missing = GNUNET_CONTAINER_multishortmap_size(ping->pong_map);
+ ping->traffic = 1;
+
+ GNUNET_CONTAINER_multihashmap_put(tool->ping_map, hash, ping,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
+
+ if (0 >= ping->pong_missing)
+ finish_ping (tool, ping, room, hash);
+
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ else
+ {
+ struct GNUNET_CONTAINER_MultiHashMapIterator *iter =
+ GNUNET_CONTAINER_multihashmap_iterator_create(tool->ping_map);
+
+ struct GNUNET_HashCode key;
+ const void *value;
+
+ while (GNUNET_NO != GNUNET_CONTAINER_multihashmap_iterator_next(iter, &key, &value))
+ {
+ struct GNUNET_MESSENGER_Ping *ping = (struct GNUNET_MESSENGER_Ping*) value;
+
+ if (0 >= ping->pong_missing)
+ continue;
+
+ ping->traffic++;
+
+ if (NULL != GNUNET_CONTAINER_multishortmap_get(ping->pong_map, hash_contact (sender)))
+ continue;
+
+ if (!is_hash_following (tool, hash, &key))
+ continue;
+
+ struct GNUNET_TIME_Absolute *time = GNUNET_new(struct GNUNET_TIME_Absolute);
+ *time = GNUNET_TIME_absolute_ntoh(message->header.timestamp);
+
+ {
+ struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
+ ping->ping_time, *time);
+
+ printf("%s as response to %s from: sender=%lu time=%.3f ms\n",
+ GNUNET_MESSENGER_name_of_kind(message->header.kind),
+ GNUNET_h2s(&key),
+ GNUNET_MESSENGER_contact_get_id(sender),
+ ((float) difference.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
+ }
+
+ GNUNET_CONTAINER_multishortmap_put(ping->pong_map, hash_contact (sender), time,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
+
+ ping->pong_missing--;
+ if (0 < ping->pong_missing)
+ continue;
+
+ finish_ping (tool, ping, room, &key);
+ }
+
+ GNUNET_CONTAINER_multihashmap_iterator_destroy(iter);
+ }
+
+skip_ping:
+ if (!(tool->quit))
+ {
+ if (tool->task)
+ GNUNET_SCHEDULER_cancel(tool->task);
+
+ tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
+ GNUNET_TIME_relative_get_second_(),
+ GNUNET_SCHEDULER_PRIORITY_IDLE,
+ idle,
+ tool
+ );
+ }
+ else if (!(tool->task))
+ tool->task = GNUNET_SCHEDULER_add_with_priority(
+ GNUNET_SCHEDULER_PRIORITY_IDLE,
+ idle,
+ tool
+ );
+
+ if (tool->quit)
+ GNUNET_MESSENGER_close_room(room);
+}
+
+static void
+ego_lookup (void *cls,
+ struct GNUNET_IDENTITY_Ego *ego)
+{
+ struct GNUNET_MESSENGER_PingTool *tool = cls;
+
+ tool->lookup = NULL;
+
+ const struct GNUNET_CRYPTO_PrivateKey *key;
+ key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL;
+
+ tool->handle = GNUNET_MESSENGER_connect(
+ tool->cfg,
+ tool->ego_name,
+ key,
+ message_callback,
+ tool
+ );
+
+ struct GNUNET_PeerIdentity peer;
+ GNUNET_CRYPTO_get_peer_identity(
+ tool->cfg,
+ &peer
+ );
+
+ if (tool->auto_pong)
+ printf("PONG ");
+ else
+ printf("PING ");
+
+ printf("%s", GNUNET_i2s(&peer));
+
+ struct GNUNET_HashCode hash;
+ if (tool->room_name)
+ {
+ printf(":%s", tool->room_name);
+
+ GNUNET_CRYPTO_hash(
+ tool->room_name,
+ strlen(tool->room_name),
+ &hash
+ );
+ }
+ else
+ memset(&hash, 0, sizeof(hash));
+
+ printf(" (%s): %u times\n",
+ GNUNET_h2s(&hash), tool->count);
+
+ struct GNUNET_MESSENGER_Room *room;
+ room = GNUNET_MESSENGER_enter_room(
+ tool->handle,
+ &peer,
+ &hash
+ );
+
+ if (room)
+ GNUNET_MESSENGER_use_room_keys(
+ room, tool->public_room? GNUNET_YES : GNUNET_NO);
+}
+
+static void
+run (void *cls,
+ char* const* args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_MESSENGER_PingTool *tool = cls;
+
+ tool->cfg = cfg;
+
+ if (!(tool->ego_name))
+ {
+ ego_lookup(tool, NULL);
+ return;
+ }
+
+ tool->lookup = GNUNET_IDENTITY_ego_lookup(
+ cfg,
+ tool->ego_name,
+ &ego_lookup,
+ tool
+ );
+}
+
+int
+main (int argc,
+ char* const* argv)
+{
+ struct GNUNET_MESSENGER_PingTool tool;
+ memset(&tool, 0, sizeof(tool));
+
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string(
+ 'e',
+ "ego",
+ "IDENTITY_NAME",
+ "name of identity to send/receive messages with",
+ &(tool.ego_name)
+ ),
+ GNUNET_GETOPT_option_string(
+ 'r',
+ "room",
+ "ROOM_NAME",
+ "name of room to read messages from",
+ &(tool.room_name)
+ ),
+ GNUNET_GETOPT_option_uint(
+ 'c',
+ "count",
+ "<count>",
+ "stop after a count of iterations",
+ &(tool.count)
+ ),
+ GNUNET_GETOPT_option_flag(
+ 'p',
+ "public",
+ "disable forward secrecy for public rooms",
+ &(tool.public_room)
+ ),
+ GNUNET_GETOPT_option_flag(
+ 'P',
+ "pong",
+ "only send back messages after a ping",
+ &(tool.auto_pong)
+ ),
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
+ tool.ping_map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
+
+ enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run(
+ argc,
+ argv,
+ "gnunet_messenger_ping",
+ gettext_noop("A tool to measure latency in the Messenger service of GNUnet."),
+ options,
+ &run,
+ &tool
+ );
+
+ GNUNET_CONTAINER_multihashmap_destroy(tool.ping_map);
+ GNUNET_CONTAINER_multihashmap_destroy(tool.map);
+
+ return GNUNET_OK == result? 0 : 1;
+}
diff --git a/tools/gnunet_messenger_uml.c b/tools/gnunet_messenger_uml.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2024 GNUnet e.V.
+ Copyright (C) 2024--2025 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -494,12 +494,12 @@ main (int argc,
"ignore indirect connections between epoch messages and their previous epoch",
&(tool.ignore_epochs)
),
- GNUNET_GETOPT_option_flag(
+ GNUNET_GETOPT_option_flag(
'm',
"simplify-merges",
"simplify merge messages in the message graph",
&(tool.simplify_merges)
- ),
+ ),
GNUNET_GETOPT_OPTION_END
};
diff --git a/tools/meson.build b/tools/meson.build
@@ -1,6 +1,6 @@
#
# This file is part of GNUnet.
-# Copyright (C) 2024 GNUnet e.V.
+# Copyright (C) 2024--2025 GNUnet e.V.
#
# GNUnet is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
@@ -37,3 +37,14 @@ messenger_uml = executable(
dependency('gnunetutil')
],
)
+
+messenger_ping = executable(
+ 'messenger_ping',
+ [ 'gnunet_messenger_ping.c' ],
+ dependencies: [
+ dependency('gnunetidentity'),
+ dependency('gnunetmessenger'),
+ dependency('gnunetutil')
+ ],
+ link_args: '-lm'
+)