gnunet_messenger_ping.c (18964B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2025 GNUnet e.V. 4 5 GNUnet is free software: you can redistribute it and/or modify it 6 under the terms of the GNU Affero General Public License as published 7 by the Free Software Foundation, either version 3 of the License, 8 or (at your option) any later version. 9 10 GNUnet is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 SPDX-License-Identifier: AGPL3.0-or-later 19 */ 20 /* 21 * @author Tobias Frisch 22 * @file gnunet_messenger_ping.c 23 */ 24 25 #include <gnunet/gnunet_common.h> 26 #include <gnunet/gnunet_identity_service.h> 27 #include <gnunet/gnunet_messenger_service.h> 28 #include <gnunet/gnunet_scheduler_lib.h> 29 #include <gnunet/gnunet_time_lib.h> 30 #include <gnunet/gnunet_util_lib.h> 31 #include <stdint.h> 32 #include <math.h> 33 #include <stdio.h> 34 #include <string.h> 35 36 struct GNUNET_MESSENGER_Ping 37 { 38 struct GNUNET_HashCode hash; 39 40 struct GNUNET_TIME_Absolute ping_time; 41 const struct GNUNET_MESSENGER_Contact *sender; 42 43 struct GNUNET_CONTAINER_MultiShortmap *pong_map; 44 45 size_t pong_missing; 46 size_t traffic; 47 }; 48 49 struct GNUNET_MESSENGER_PingTool 50 { 51 const struct GNUNET_CONFIGURATION_Handle *cfg; 52 struct GNUNET_IDENTITY_EgoLookup *lookup; 53 struct GNUNET_MESSENGER_Handle *handle; 54 struct GNUNET_MESSENGER_Room *room; 55 struct GNUNET_SCHEDULER_Task *hook; 56 struct GNUNET_SCHEDULER_Task *task; 57 58 struct GNUNET_CONTAINER_MultiHashMap *map; 59 struct GNUNET_CONTAINER_MultiHashMap *ping_map; 60 struct GNUNET_MESSENGER_Ping *last_ping; 61 62 char *ego_name; 63 char *room_name; 64 uint count; 65 uint timeout; 66 uint delay; 67 int public_room; 68 int auto_pong; 69 int join_trigger; 70 71 bool permanent; 72 size_t counter; 73 }; 74 75 static const struct GNUNET_ShortHashCode* 76 hash_contact (const struct GNUNET_MESSENGER_Contact *contact) 77 { 78 static struct GNUNET_ShortHashCode hash; 79 memset(&hash, 0, sizeof (hash)); 80 81 size_t id = GNUNET_MESSENGER_contact_get_id(contact); 82 GNUNET_memcpy(&hash, &id, sizeof (id)); 83 84 return &hash; 85 } 86 87 static void 88 finish_ping (struct GNUNET_MESSENGER_PingTool *tool, 89 struct GNUNET_MESSENGER_Ping *ping, 90 struct GNUNET_MESSENGER_Room *room); 91 92 static void 93 cleanup (void *cls) 94 { 95 struct GNUNET_MESSENGER_PingTool *tool = cls; 96 97 tool->task = NULL; 98 99 if (tool->last_ping) 100 finish_ping(tool, tool->last_ping, tool->room); 101 102 if (tool->hook) 103 { 104 GNUNET_SCHEDULER_cancel (tool->hook); 105 tool->hook = NULL; 106 } 107 108 if (tool->room) 109 { 110 GNUNET_MESSENGER_close_room(tool->room); 111 tool->room = NULL; 112 } 113 114 if (tool->handle) 115 { 116 GNUNET_MESSENGER_disconnect(tool->handle); 117 tool->handle = NULL; 118 } 119 120 if (tool->lookup) 121 { 122 GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup); 123 tool->lookup = NULL; 124 } 125 } 126 127 static void 128 shutdown_hook (void *cls) 129 { 130 struct GNUNET_MESSENGER_PingTool *tool = cls; 131 132 tool->hook = NULL; 133 tool->permanent = false; 134 135 if (tool->task) 136 { 137 GNUNET_SCHEDULER_cancel(tool->task); 138 tool->task = NULL; 139 } 140 141 cleanup(cls); 142 } 143 144 static void 145 finish (void *cls) 146 { 147 struct GNUNET_MESSENGER_PingTool *tool = cls; 148 149 tool->task = NULL; 150 151 if (tool->room) 152 { 153 GNUNET_MESSENGER_close_room(tool->room); 154 tool->room = NULL; 155 } 156 } 157 158 static void 159 send_ping (struct GNUNET_MESSENGER_PingTool *tool, 160 struct GNUNET_MESSENGER_Room *room) 161 { 162 struct GNUNET_MESSENGER_Message message; 163 message.header.kind = GNUNET_MESSENGER_KIND_TEXT; 164 message.body.text.text = NULL; 165 166 GNUNET_MESSENGER_send_message(room, &message, NULL); 167 tool->counter++; 168 } 169 170 static void 171 send_pong (struct GNUNET_MESSENGER_PingTool *tool, 172 struct GNUNET_MESSENGER_Room *room, 173 const struct GNUNET_HashCode *hash, 174 const struct GNUNET_TIME_Absolute timestamp) 175 { 176 struct GNUNET_MESSENGER_Message message; 177 message.header.kind = GNUNET_MESSENGER_KIND_TAG; 178 message.body.tag.tag = NULL; 179 180 GNUNET_memcpy(&(message.body.tag.hash), hash, sizeof(*hash)); 181 182 const struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference( 183 timestamp, GNUNET_TIME_absolute_get()); 184 185 printf("%s as response to %s from: time=%.3f ms\n", 186 GNUNET_MESSENGER_name_of_kind(message.header.kind), 187 GNUNET_h2s(hash), 188 ((float) difference.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us); 189 190 GNUNET_MESSENGER_send_message(room, &message, NULL); 191 tool->counter++; 192 193 if ((!(tool->permanent)) && (tool->counter >= tool->count)) 194 { 195 if (tool->task) 196 GNUNET_SCHEDULER_cancel(tool->task); 197 198 tool->task = GNUNET_SCHEDULER_add_delayed_with_priority( 199 GNUNET_TIME_relative_get_second_(), 200 GNUNET_SCHEDULER_PRIORITY_IDLE, 201 finish, 202 tool); 203 } 204 } 205 206 static void 207 delay_ping (void *cls) 208 { 209 struct GNUNET_MESSENGER_PingTool *tool = cls; 210 211 tool->task = NULL; 212 213 if (tool->join_trigger) 214 return; 215 216 send_ping(tool, tool->room); 217 } 218 219 static void 220 finish_ping (struct GNUNET_MESSENGER_PingTool *tool, 221 struct GNUNET_MESSENGER_Ping *ping, 222 struct GNUNET_MESSENGER_Room *room) 223 { 224 const size_t recipients = GNUNET_CONTAINER_multishortmap_size(ping->pong_map); 225 const size_t loss_rate = recipients? 100 * ping->pong_missing / recipients : 100; 226 const struct GNUNET_TIME_Relative delta = GNUNET_TIME_absolute_get_difference( 227 ping->ping_time, GNUNET_TIME_absolute_get()); 228 229 printf("--- %s ping statistics ---\n", GNUNET_h2s(&(ping->hash))); 230 231 struct GNUNET_TIME_Relative min = GNUNET_TIME_relative_get_forever_(); 232 struct GNUNET_TIME_Relative avg = GNUNET_TIME_relative_get_zero_(); 233 struct GNUNET_TIME_Relative max = GNUNET_TIME_relative_get_zero_(); 234 struct GNUNET_TIME_Relative mdev = GNUNET_TIME_relative_get_zero_(); 235 236 struct GNUNET_CONTAINER_MultiShortmapIterator *iter; 237 const void *value; 238 239 iter = GNUNET_CONTAINER_multishortmap_iterator_create(ping->pong_map); 240 241 while (GNUNET_NO != GNUNET_CONTAINER_multishortmap_iterator_next(iter, NULL, &value)) 242 { 243 if (!value) 244 continue; 245 246 const struct GNUNET_TIME_Absolute *time = value; 247 struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference( 248 ping->ping_time, *time); 249 250 if (GNUNET_TIME_relative_cmp(difference, <, min)) 251 min = difference; 252 if (GNUNET_TIME_relative_cmp(difference, >, max)) 253 max = difference; 254 255 avg = GNUNET_TIME_relative_add(avg, difference); 256 } 257 258 GNUNET_CONTAINER_multishortmap_iterator_destroy(iter); 259 260 if (recipients > ping->pong_missing) 261 avg = GNUNET_TIME_relative_divide(avg, recipients - ping->pong_missing); 262 263 iter = GNUNET_CONTAINER_multishortmap_iterator_create(ping->pong_map); 264 265 while (GNUNET_NO != GNUNET_CONTAINER_multishortmap_iterator_next(iter, NULL, &value)) 266 { 267 if (!value) 268 continue; 269 270 const struct GNUNET_TIME_Absolute *time = value; 271 struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference( 272 ping->ping_time, *time); 273 274 difference = GNUNET_TIME_relative_subtract(difference, avg); 275 difference = GNUNET_TIME_relative_saturating_multiply(difference, 276 difference.rel_value_us); 277 278 mdev = GNUNET_TIME_relative_add(mdev, difference); 279 } 280 281 GNUNET_CONTAINER_multishortmap_iterator_destroy(iter); 282 283 if (recipients > ping->pong_missing) 284 mdev = GNUNET_TIME_relative_divide(mdev, recipients - ping->pong_missing); 285 286 mdev.rel_value_us = (uint64_t) sqrt(mdev.rel_value_us); 287 288 printf("%lu messages exchanged, %lu recipients, %lu%% message loss, time %.3fms\n", 289 ping->traffic, recipients, loss_rate, ((float) delta.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us); 290 291 if (recipients > 0) 292 printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n\n", 293 ((float) min.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us, 294 ((float) avg.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us, 295 ((float) max.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us, 296 ((float) mdev.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us); 297 298 if (ping == tool->last_ping) 299 tool->last_ping = NULL; 300 301 if (tool->task) 302 GNUNET_SCHEDULER_cancel(tool->task); 303 304 if ((tool->permanent) || (tool->counter < tool->count)) 305 tool->task = GNUNET_SCHEDULER_add_delayed_with_priority( 306 GNUNET_TIME_relative_multiply(GNUNET_TIME_relative_get_second_(), tool->delay), 307 GNUNET_SCHEDULER_PRIORITY_IDLE, 308 delay_ping, 309 tool); 310 else 311 tool->task = GNUNET_SCHEDULER_add_delayed_with_priority( 312 GNUNET_TIME_relative_get_second_(), 313 GNUNET_SCHEDULER_PRIORITY_IDLE, 314 finish, 315 tool); 316 } 317 318 static enum GNUNET_GenericReturnValue 319 member_callback (void *cls, 320 struct GNUNET_MESSENGER_Room *room, 321 const struct GNUNET_MESSENGER_Contact *contact) 322 { 323 struct GNUNET_MESSENGER_Ping *ping = cls; 324 325 if (contact == ping->sender) 326 return GNUNET_YES; 327 328 GNUNET_CONTAINER_multishortmap_put(ping->pong_map, hash_contact (contact), NULL, 329 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); 330 331 return GNUNET_YES; 332 } 333 334 static void 335 message_callback (void *cls, 336 struct GNUNET_MESSENGER_Room *room, 337 const struct GNUNET_MESSENGER_Contact *sender, 338 const struct GNUNET_MESSENGER_Contact *recipient, 339 const struct GNUNET_MESSENGER_Message *message, 340 const struct GNUNET_HashCode *hash, 341 enum GNUNET_MESSENGER_MessageFlags flags) 342 { 343 struct GNUNET_MESSENGER_PingTool *tool = cls; 344 345 if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains(tool->map, hash)) 346 { 347 struct GNUNET_HashCode *copy = GNUNET_malloc(sizeof(struct GNUNET_HashCode) * 2); 348 GNUNET_memcpy(copy, &(message->header.previous), sizeof (*copy)); 349 350 if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind) 351 GNUNET_memcpy(copy + 1, &(message->body.merge.previous), sizeof (*copy)); 352 else 353 GNUNET_memcpy(copy + 1, &(message->header.previous), sizeof (*copy)); 354 355 GNUNET_CONTAINER_multihashmap_put(tool->map, hash, copy, 356 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); 357 } 358 359 if (GNUNET_MESSENGER_FLAG_SENT & flags) 360 { 361 switch (message->header.kind) 362 { 363 case GNUNET_MESSENGER_KIND_JOIN: 364 { 365 if ((!(tool->auto_pong)) && (!(tool->join_trigger))) 366 send_ping(tool, room); 367 368 break; 369 } 370 case GNUNET_MESSENGER_KIND_LEAVE: 371 { 372 GNUNET_SCHEDULER_shutdown(); 373 break; 374 } 375 case GNUNET_MESSENGER_KIND_TEXT: 376 { 377 struct GNUNET_MESSENGER_Ping *ping = GNUNET_new(struct GNUNET_MESSENGER_Ping); 378 379 GNUNET_memcpy(&(ping->hash), hash, sizeof(ping->hash)); 380 381 ping->ping_time = GNUNET_TIME_absolute_ntoh(message->header.timestamp); 382 ping->sender = sender; 383 384 ping->pong_map = GNUNET_CONTAINER_multishortmap_create(8, GNUNET_NO); 385 386 GNUNET_MESSENGER_iterate_members(room, member_callback, ping); 387 388 ping->pong_missing = GNUNET_CONTAINER_multishortmap_size(ping->pong_map); 389 ping->traffic = 1; 390 391 GNUNET_CONTAINER_multihashmap_put(tool->ping_map, hash, ping, 392 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); 393 394 tool->last_ping = ping; 395 396 if (0 >= ping->pong_missing) 397 finish_ping (tool, ping, room); 398 399 break; 400 } 401 default: 402 break; 403 } 404 } 405 else if (tool->auto_pong) 406 { 407 if (GNUNET_MESSENGER_KIND_TEXT == message->header.kind) 408 send_pong(tool, room, hash, GNUNET_TIME_absolute_ntoh(message->header.timestamp)); 409 } 410 else 411 { 412 if ((tool->join_trigger) && (GNUNET_MESSENGER_KIND_JOIN == message->header.kind)) 413 send_ping(tool, room); 414 415 if (0 == GNUNET_CONTAINER_multihashmap_size (tool->ping_map)) 416 return; 417 418 struct GNUNET_CONTAINER_MultiHashMapIterator *iter = 419 GNUNET_CONTAINER_multihashmap_iterator_create(tool->ping_map); 420 421 const void *value; 422 while (GNUNET_NO != GNUNET_CONTAINER_multihashmap_iterator_next(iter, NULL, &value)) 423 { 424 struct GNUNET_MESSENGER_Ping *ping = (struct GNUNET_MESSENGER_Ping*) value; 425 426 if (0 >= ping->pong_missing) 427 continue; 428 429 ping->traffic++; 430 431 if (((GNUNET_MESSENGER_KIND_TAG != message->header.kind) || 432 (0 != GNUNET_CRYPTO_hash_cmp(&(message->body.tag.hash), &(ping->hash))))) 433 continue; 434 435 if (!sender) 436 continue; 437 438 if (GNUNET_YES != GNUNET_CONTAINER_multishortmap_contains_value(ping->pong_map, hash_contact (sender), NULL)) 439 continue; 440 441 struct GNUNET_TIME_Absolute *time = GNUNET_new(struct GNUNET_TIME_Absolute); 442 *time = GNUNET_TIME_absolute_ntoh(message->header.timestamp); 443 444 { 445 struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference( 446 ping->ping_time, *time); 447 448 printf("%s as response to %s from: sender=%lu time=%.3f ms\n", 449 GNUNET_MESSENGER_name_of_kind(message->header.kind), 450 GNUNET_h2s(&(ping->hash)), 451 GNUNET_MESSENGER_contact_get_id(sender), 452 ((float) difference.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us); 453 } 454 455 GNUNET_CONTAINER_multishortmap_put(ping->pong_map, hash_contact (sender), time, 456 GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE); 457 458 ping->pong_missing--; 459 if (0 < ping->pong_missing) 460 continue; 461 462 finish_ping (tool, ping, room); 463 } 464 465 GNUNET_CONTAINER_multihashmap_iterator_destroy(iter); 466 } 467 } 468 469 static void 470 ego_lookup (void *cls, 471 struct GNUNET_IDENTITY_Ego *ego) 472 { 473 struct GNUNET_MESSENGER_PingTool *tool = cls; 474 475 tool->lookup = NULL; 476 477 const struct GNUNET_CRYPTO_BlindablePrivateKey *key; 478 key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL; 479 480 tool->handle = GNUNET_MESSENGER_connect( 481 tool->cfg, 482 tool->ego_name, 483 key, 484 message_callback, 485 tool 486 ); 487 488 struct GNUNET_PeerIdentity peer; 489 GNUNET_CRYPTO_get_peer_identity( 490 tool->cfg, 491 &peer 492 ); 493 494 if (tool->auto_pong) 495 printf("PONG "); 496 else 497 printf("PING "); 498 499 printf("%s", GNUNET_i2s(&peer)); 500 501 union GNUNET_MESSENGER_RoomKey rkey; 502 if (tool->room_name) 503 { 504 printf(":%s", tool->room_name); 505 506 GNUNET_MESSENGER_create_room_key( 507 &rkey, 508 tool->room_name, 509 tool->public_room? GNUNET_YES : GNUNET_NO, 510 GNUNET_YES, 511 GNUNET_NO 512 ); 513 } 514 else 515 { 516 memset(&(rkey.hash), 0, sizeof(rkey.hash)); 517 518 rkey.code.public_bit = tool->public_room? 1 : 0; 519 rkey.code.group_bit = 1; 520 } 521 522 printf(" (%s): ", 523 GNUNET_h2s(&(rkey.hash))); 524 525 if (0 == tool->count) 526 { 527 printf("infinite\n"); 528 tool->permanent = true; 529 } 530 else 531 printf("%u times\n", tool->count); 532 533 tool->room = GNUNET_MESSENGER_enter_room( 534 tool->handle, 535 &peer, 536 &rkey 537 ); 538 539 if (tool->timeout) 540 tool->task = GNUNET_SCHEDULER_add_delayed_with_priority( 541 GNUNET_TIME_relative_multiply( 542 GNUNET_TIME_relative_get_second_(), tool->timeout), 543 GNUNET_SCHEDULER_PRIORITY_IDLE, 544 finish, 545 tool 546 ); 547 } 548 549 static void 550 run (void *cls, 551 char* const* args, 552 const char *cfgfile, 553 const struct GNUNET_CONFIGURATION_Handle *cfg) 554 { 555 struct GNUNET_MESSENGER_PingTool *tool = cls; 556 557 tool->cfg = cfg; 558 tool->hook = GNUNET_SCHEDULER_add_shutdown(shutdown_hook, tool); 559 560 if (!(tool->ego_name)) 561 { 562 ego_lookup(tool, NULL); 563 return; 564 } 565 566 tool->lookup = GNUNET_IDENTITY_ego_lookup( 567 cfg, 568 tool->ego_name, 569 &ego_lookup, 570 tool 571 ); 572 } 573 574 enum GNUNET_GenericReturnValue 575 free_map_time (void *cls, 576 const struct GNUNET_ShortHashCode *key, 577 void *value) 578 { 579 struct GNUNET_TIME_Absolute *time = value; 580 581 if (time) 582 GNUNET_free(time); 583 584 return GNUNET_YES; 585 } 586 587 enum GNUNET_GenericReturnValue 588 free_map_ping (void *cls, 589 const struct GNUNET_HashCode *key, 590 void *value) 591 { 592 struct GNUNET_MESSENGER_Ping *ping = value; 593 594 GNUNET_CONTAINER_multishortmap_iterate(ping->pong_map, free_map_time, NULL); 595 GNUNET_CONTAINER_multishortmap_destroy(ping->pong_map); 596 597 GNUNET_free(ping); 598 return GNUNET_YES; 599 } 600 601 enum GNUNET_GenericReturnValue 602 free_map_hashes (void *cls, 603 const struct GNUNET_HashCode *key, 604 void *value) 605 { 606 struct GNUNET_HashCode *hashes = value; 607 GNUNET_free(hashes); 608 return GNUNET_YES; 609 } 610 611 int 612 main (int argc, 613 char* const* argv) 614 { 615 struct GNUNET_MESSENGER_PingTool tool; 616 memset(&tool, 0, sizeof(tool)); 617 618 const struct GNUNET_OS_ProjectData *data; 619 data = GNUNET_OS_project_data_gnunet (); 620 621 struct GNUNET_GETOPT_CommandLineOption options[] = { 622 GNUNET_GETOPT_option_string( 623 'e', 624 "ego", 625 "IDENTITY_NAME", 626 "name of identity to send/receive messages with", 627 &(tool.ego_name) 628 ), 629 GNUNET_GETOPT_option_string( 630 'r', 631 "room", 632 "ROOM_NAME", 633 "name of room to read messages from", 634 &(tool.room_name) 635 ), 636 GNUNET_GETOPT_option_uint( 637 'c', 638 "count", 639 "<count>", 640 "stop after a count of iterations", 641 &(tool.count) 642 ), 643 GNUNET_GETOPT_option_uint( 644 't', 645 "timeout", 646 "<timeout>", 647 "stop after a timeout in seconds", 648 &(tool.timeout) 649 ), 650 GNUNET_GETOPT_option_uint( 651 'd', 652 "delay", 653 "<delay>", 654 "delay next iteration in seconds", 655 &(tool.delay) 656 ), 657 GNUNET_GETOPT_option_flag( 658 'p', 659 "public", 660 "disable forward secrecy for public rooms", 661 &(tool.public_room) 662 ), 663 GNUNET_GETOPT_option_flag( 664 'P', 665 "pong", 666 "only send back pong messages after a ping", 667 &(tool.auto_pong) 668 ), 669 GNUNET_GETOPT_option_flag( 670 'J', 671 "join-trigger", 672 "only send a ping message after join events", 673 &(tool.join_trigger) 674 ), 675 GNUNET_GETOPT_OPTION_END 676 }; 677 678 tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO); 679 tool.ping_map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO); 680 681 enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run( 682 data, 683 argc, 684 argv, 685 "gnunet_messenger_ping", 686 gettext_noop("A tool to measure latency in the Messenger service of GNUnet."), 687 options, 688 &run, 689 &tool 690 ); 691 692 printf("--- %lu iteration%s done ---\n", tool.counter, tool.counter == 1? "" : "s"); 693 694 GNUNET_CONTAINER_multihashmap_iterate(tool.ping_map, free_map_ping, NULL); 695 GNUNET_CONTAINER_multihashmap_iterate(tool.map, free_map_hashes, NULL); 696 697 GNUNET_CONTAINER_multihashmap_destroy(tool.ping_map); 698 GNUNET_CONTAINER_multihashmap_destroy(tool.map); 699 700 return GNUNET_OK == result? 0 : 1; 701 }