gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnunet] branch master updated (25ef40ef7 -> d68731944)


From: gnunet
Subject: [gnunet] branch master updated (25ef40ef7 -> d68731944)
Date: Sat, 19 Feb 2022 16:20:40 +0100

This is an automated email from the git hooks/post-receive script.

grothoff pushed a change to branch master
in repository gnunet.

    from 25ef40ef7 -style fixes
     new ba9296269 first steps towards usable dhtu
     new cd84bf32e -also add serialization from/to block
     new 3e3081cfd incomplete first hack of new hello-uri lib
     new af252f5c3 -conclude hello-uri implementation and test
     new ac77d5f43 add new HELLO_URI block support to block plugin
     new 3a7115340 -implement TVG
     new b0abdf712 -more work on DHTU integration
     new 0f9ccaea0 rebase
     new 4c3b6b219 -DHT now takes care of queue size limit
     new a98b54841 -add hold/drop logic
     new 8ca9b2754 -DHT: add gnunet-dht-hello for bootstrapping
     new 7236e5f83 -fix crashes on new DHT load/shutdown
     new 980eec8b7 -fix port initialization in addr
     new e5f00fda5 -DHT: get tests to pass
     new 0bd15d392 -adding logging, minor dthu bugfixes
     new 5e041b56a -got basics to work with dhtu and udp+ip underlay
     new 5b4ac38b5 -try to make static analysis happy
     new 901b2fcc5 -fix merge issues
     new 95ffa9b9a -fix htons/htonl bug introduced by message format change
     new d68731944 -fix assertion, fix key initialization

The 20 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 po/POTFILES.in                            |    4 +-
 src/datacache/datacache.c                 |   86 +-
 src/datacache/plugin_datacache_heap.c     |   54 +-
 src/datacache/plugin_datacache_postgres.c |   16 +-
 src/datacache/plugin_datacache_sqlite.c   |   52 +-
 src/datacache/plugin_datacache_template.c |    2 +
 src/dht/.gitignore                        |    1 +
 src/dht/Makefile.am                       |   16 +-
 src/dht/dht_api.c                         |  168 +++-
 src/dht/gnunet-dht-get.c                  |   24 +-
 src/dht/gnunet-dht-hello.c                |  178 ++++
 src/dht/gnunet-service-dht.c              |  467 +++++++++-
 src/dht/gnunet-service-dht.h              |   90 +-
 src/dht/gnunet-service-dht_clients.c      |  112 ++-
 src/dht/gnunet-service-dht_datacache.c    |  124 +--
 src/dht/gnunet-service-dht_datacache.h    |   11 +-
 src/dht/gnunet-service-dht_hello.c        |  154 ---
 src/dht/gnunet-service-dht_hello.h        |   55 --
 src/dht/gnunet-service-dht_neighbours.c   | 1448 +++++++++++++++--------------
 src/dht/gnunet-service-dht_neighbours.h   |   81 +-
 src/dht/gnunet-service-dht_nse.c          |  121 ---
 src/dht/gnunet-service-dht_nse.h          |   52 --
 src/dht/gnunet-service-dht_routing.c      |    2 +-
 src/dht/plugin_block_dht.c                |  400 +++++---
 src/dhtu/Makefile.am                      |    4 +
 src/dhtu/dhtu.conf                        |    7 +
 src/dhtu/plugin_dhtu_gnunet.c             |   62 +-
 src/dhtu/plugin_dhtu_ip.c                 |  434 ++++++---
 src/gnsrecord/gnunet-gnsrecord-tvg.c      |   65 +-
 src/hello/.gitignore                      |    2 +
 src/hello/Makefile.am                     |   19 +-
 src/hello/hello-uri.c                     |  891 ++++++++++++++++++
 src/hello/test_hello-ng.c                 |   23 +-
 src/hello/test_hello-uri.c                |  212 +++++
 src/include/Makefile.am                   |    1 +
 src/include/gnunet_block_lib.h            |    6 +
 src/include/gnunet_datacache_lib.h        |    2 +
 src/include/gnunet_datacache_plugin.h     |    2 +
 src/include/gnunet_dht_service.h          |   67 +-
 src/include/gnunet_dhtu_plugin.h          |   26 +-
 src/include/gnunet_hello_uri_lib.h        |  248 +++++
 src/include/gnunet_protocols.h            |   24 +-
 src/include/gnunet_strings_lib.h          |    2 +-
 src/include/gnunet_time_lib.h             |   13 +-
 src/transport/gnunet-service-transport.c  |    4 +-
 src/util/network.c                        |    3 +-
 src/util/plugin.c                         |    4 +-
 src/util/strings.c                        |    9 +-
 src/util/time.c                           |    7 +
 49 files changed, 4166 insertions(+), 1689 deletions(-)
 create mode 100644 src/dht/gnunet-dht-hello.c
 delete mode 100644 src/dht/gnunet-service-dht_hello.c
 delete mode 100644 src/dht/gnunet-service-dht_hello.h
 delete mode 100644 src/dht/gnunet-service-dht_nse.c
 delete mode 100644 src/dht/gnunet-service-dht_nse.h
 create mode 100644 src/dhtu/dhtu.conf
 create mode 100644 src/hello/hello-uri.c
 create mode 100644 src/hello/test_hello-uri.c
 create mode 100644 src/include/gnunet_hello_uri_lib.h

diff --git a/po/POTFILES.in b/po/POTFILES.in
index ec5600f77..49cb3854f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -102,9 +102,7 @@ src/dht/gnunet-dht-put.c
 src/dht/gnunet-service-dht.c
 src/dht/gnunet-service-dht_clients.c
 src/dht/gnunet-service-dht_datacache.c
-src/dht/gnunet-service-dht_hello.c
 src/dht/gnunet-service-dht_neighbours.c
-src/dht/gnunet-service-dht_nse.c
 src/dht/gnunet-service-dht_routing.c
 src/dht/gnunet_dht_profiler.c
 src/dht/plugin_block_dht.c
@@ -186,6 +184,7 @@ src/gnsrecord/plugin_gnsrecord_dns.c
 src/hello/address.c
 src/hello/gnunet-hello.c
 src/hello/hello-ng.c
+src/hello/hello-uri.c
 src/hello/hello.c
 src/hostlist/gnunet-daemon-hostlist.c
 src/hostlist/gnunet-daemon-hostlist_client.c
@@ -587,7 +586,6 @@ src/vpn/gnunet-vpn.c
 src/vpn/vpn_api.c
 src/zonemaster/gnunet-service-zonemaster-monitor.c
 src/zonemaster/gnunet-service-zonemaster.c
-src/zonemaster/zonemaster_misc.c
 src/fs/fs_api.h
 src/include/gnunet_json_lib.h
 src/testbed/testbed_api.h
diff --git a/src/datacache/datacache.c b/src/datacache/datacache.c
index 944a99aad..761ab801f 100644
--- a/src/datacache/datacache.c
+++ b/src/datacache/datacache.c
@@ -114,11 +114,11 @@ env_delete_notify (void *cls,
   h->utilization -= size;
   GNUNET_CONTAINER_bloomfilter_remove (h->filter, key);
   GNUNET_STATISTICS_update (h->stats,
-                            gettext_noop ("# bytes stored"),
+                            "# bytes stored",
                             -(long long) size,
                             GNUNET_NO);
   GNUNET_STATISTICS_update (h->stats,
-                            gettext_noop ("# items stored"),
+                            "# items stored",
                             -1,
                             GNUNET_NO);
 }
@@ -136,15 +136,25 @@ GNUNET_DATACACHE_create (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
   const struct GNUNET_OS_ProjectData *pd;
 
   if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_size (cfg, section, "QUOTA", &quota))
+      GNUNET_CONFIGURATION_get_value_size (cfg,
+                                           section,
+                                           "QUOTA",
+                                           &quota))
   {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "QUOTA");
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "QUOTA");
     return NULL;
   }
   if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_string (cfg, section, "DATABASE", &name))
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             "DATABASE",
+                                             &name))
   {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "DATABASE");
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "DATABASE");
     return NULL;
   }
   bf_size = quota / 32; /* 8 bit per entry, 1 bit per 32 kb in DB */
@@ -152,10 +162,14 @@ GNUNET_DATACACHE_create (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
   ret = GNUNET_new (struct GNUNET_DATACACHE_Handle);
 
   if (GNUNET_YES !=
-      GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "DISABLE_BF"))
+      GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                            section,
+                                            "DISABLE_BF"))
   {
     if (GNUNET_YES !=
-        GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "DISABLE_BF_RC"))
+        GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                              section,
+                                              "DISABLE_BF_RC"))
     {
       ret->bloom_name = GNUNET_DISK_mktemp ("gnunet-datacachebloom");
     }
@@ -174,7 +188,8 @@ GNUNET_DATACACHE_create (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
                                            5);    /* approx. 3% false 
positives at max use */
     }
   }
-  ret->stats = GNUNET_STATISTICS_create ("datacache", cfg);
+  ret->stats = GNUNET_STATISTICS_create ("datacache",
+                                         cfg);
   ret->section = GNUNET_strdup (section);
   ret->env.cfg = cfg;
   ret->env.delete_notify = &env_delete_notify;
@@ -182,25 +197,31 @@ GNUNET_DATACACHE_create (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
   ret->env.cls = ret;
   ret->env.delete_notify = &env_delete_notify;
   ret->env.quota = quota;
-  LOG (GNUNET_ERROR_TYPE_INFO, _ ("Loading `%s' datacache plugin\n"), name);
-  GNUNET_asprintf (&libname, "libgnunet_plugin_datacache_%s", name);
+  LOG (GNUNET_ERROR_TYPE_INFO,
+       "Loading `%s' datacache plugin\n",
+       name);
+  GNUNET_asprintf (&libname,
+                   "libgnunet_plugin_datacache_%s",
+                   name);
   ret->short_name = name;
   ret->lib_name = libname;
   /* Load the plugin within GNUnet's default context */
   pd = GNUNET_OS_project_data_get ();
   GNUNET_OS_init (GNUNET_OS_project_data_default ());
-  ret->api = GNUNET_PLUGIN_load (libname, &ret->env);
+  ret->api = GNUNET_PLUGIN_load (libname,
+                                 &ret->env);
   GNUNET_OS_init (pd);
   if (NULL == ret->api)
   {
     /* Try to load the plugin within the application's context
        This normally happens when the application is not GNUnet itself but a
        third party; inside GNUnet this is effectively a double failure. */
-    ret->api = GNUNET_PLUGIN_load (libname, &ret->env);
+    ret->api = GNUNET_PLUGIN_load (libname,
+                                   &ret->env);
     if (NULL == ret->api)
     {
       LOG (GNUNET_ERROR_TYPE_ERROR,
-           _ ("Failed to load datacache plugin for `%s'\n"),
+           "Failed to load datacache plugin for `%s'\n",
            name);
       GNUNET_DATACACHE_destroy (ret);
       return NULL;
@@ -216,7 +237,9 @@ GNUNET_DATACACHE_destroy (struct GNUNET_DATACACHE_Handle *h)
   if (NULL != h->filter)
     GNUNET_CONTAINER_bloomfilter_free (h->filter);
   if (NULL != h->api)
-    GNUNET_break (NULL == GNUNET_PLUGIN_unload (h->lib_name, h->api));
+    GNUNET_break (NULL ==
+                  GNUNET_PLUGIN_unload (h->lib_name,
+                                        h->api));
   GNUNET_free (h->lib_name);
   GNUNET_free (h->short_name);
   GNUNET_free (h->section);
@@ -270,17 +293,19 @@ GNUNET_DATACACHE_put (struct GNUNET_DATACACHE_Handle *h,
        "Stored data under key `%s' in cache\n",
        GNUNET_h2s (key));
   if (NULL != h->filter)
-    GNUNET_CONTAINER_bloomfilter_add (h->filter, key);
+    GNUNET_CONTAINER_bloomfilter_add (h->filter,
+                                      key);
   GNUNET_STATISTICS_update (h->stats,
-                            gettext_noop ("# bytes stored"),
+                            "# bytes stored",
                             used,
                             GNUNET_NO);
   GNUNET_STATISTICS_update (h->stats,
-                            gettext_noop ("# items stored"),
+                            "# items stored",
                             1,
                             GNUNET_NO);
   while (h->utilization + used > h->env.quota)
-    GNUNET_assert (GNUNET_OK == h->api->del (h->api->cls));
+    GNUNET_assert (GNUNET_OK ==
+                   h->api->del (h->api->cls));
   h->utilization += used;
   return GNUNET_OK;
 }
@@ -294,18 +319,18 @@ GNUNET_DATACACHE_get (struct GNUNET_DATACACHE_Handle *h,
                       void *iter_cls)
 {
   GNUNET_STATISTICS_update (h->stats,
-                            gettext_noop ("# requests received"),
+                            "# requests received",
                             1,
                             GNUNET_NO);
   LOG (GNUNET_ERROR_TYPE_DEBUG,
        "Processing request for key `%s'\n",
        GNUNET_h2s (key));
   if ((NULL != h->filter) &&
-      (GNUNET_OK != GNUNET_CONTAINER_bloomfilter_test (h->filter, key)))
+      (GNUNET_OK !=
+       GNUNET_CONTAINER_bloomfilter_test (h->filter, key)))
   {
     GNUNET_STATISTICS_update (h->stats,
-                              gettext_noop (
-                                "# requests filtered by bloom filter"),
+                              "# requests filtered by bloom filter",
                               1,
                               GNUNET_NO);
     LOG (GNUNET_ERROR_TYPE_DEBUG,
@@ -313,26 +338,33 @@ GNUNET_DATACACHE_get (struct GNUNET_DATACACHE_Handle *h,
          GNUNET_h2s (key));
     return 0;   /* can not be present */
   }
-  return h->api->get (h->api->cls, key, type, iter, iter_cls);
+  return h->api->get (h->api->cls,
+                      key,
+                      type,
+                      iter, iter_cls);
 }
 
 
 unsigned int
 GNUNET_DATACACHE_get_closest (struct GNUNET_DATACACHE_Handle *h,
                               const struct GNUNET_HashCode *key,
+                              enum GNUNET_BLOCK_Type type,
                               unsigned int num_results,
                               GNUNET_DATACACHE_Iterator iter,
                               void *iter_cls)
 {
   GNUNET_STATISTICS_update (h->stats,
-                            gettext_noop (
-                              "# proximity search requests received"),
+                            "# proximity search requests received",
                             1,
                             GNUNET_NO);
   LOG (GNUNET_ERROR_TYPE_DEBUG,
        "Processing proximity search at `%s'\n",
        GNUNET_h2s (key));
-  return h->api->get_closest (h->api->cls, key, num_results, iter, iter_cls);
+  return h->api->get_closest (h->api->cls,
+                              key,
+                              type,
+                              num_results,
+                              iter, iter_cls);
 }
 
 
diff --git a/src/datacache/plugin_datacache_heap.c 
b/src/datacache/plugin_datacache_heap.c
index fbd3aea9a..09e66d892 100644
--- a/src/datacache/plugin_datacache_heap.c
+++ b/src/datacache/plugin_datacache_heap.c
@@ -158,7 +158,7 @@ struct PutContext
  * @param value an existing value
  * @return #GNUNET_YES if not found (to continue to iterate)
  */
-static int
+static enum GNUNET_GenericReturnValue
 put_cb (void *cls,
         const struct GNUNET_HashCode *key,
         void *value)
@@ -224,15 +224,20 @@ heap_plugin_put (void *cls,
 {
   struct Plugin *plugin = cls;
   struct Value *val;
-  struct PutContext put_ctx;
-
-  put_ctx.found = GNUNET_NO;
-  put_ctx.data = data;
-  put_ctx.size = size;
-  put_ctx.path_info = path_info;
-  put_ctx.path_info_len = path_info_len;
-  put_ctx.discard_time = discard_time;
-  put_ctx.type = type;
+  struct PutContext put_ctx = {
+    .data = data,
+    .size = size,
+    .path_info = path_info,
+    .path_info_len = path_info_len,
+    .discard_time = discard_time,
+    .type = type
+  };
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Storing %u bytes under key %s with path length %u\n",
+              (unsigned int) size,
+              GNUNET_h2s (key),
+              path_info_len);
   GNUNET_CONTAINER_multihashmap_get_multiple (plugin->map,
                                               key,
                                               &put_cb,
@@ -313,12 +318,25 @@ get_cb (void *cls,
   struct Value *val = value;
   int ret;
 
-  if ((get_ctx->type != val->type) &&
-      (GNUNET_BLOCK_TYPE_ANY != get_ctx->type))
+  if ( (get_ctx->type != val->type) &&
+       (GNUNET_BLOCK_TYPE_ANY != get_ctx->type) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Result for key %s does not match block type %d\n",
+                GNUNET_h2s (key),
+                get_ctx->type);
     return GNUNET_OK;
-  if (0 ==
-      GNUNET_TIME_absolute_get_remaining (val->discard_time).rel_value_us)
+  }
+  if (GNUNET_TIME_absolute_is_past (val->discard_time))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Result for key %s is expired\n",
+                GNUNET_h2s (key));
     return GNUNET_OK;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Found result for key %s\n",
+              GNUNET_h2s (key));
   if (NULL != get_ctx->iter)
     ret = get_ctx->iter (get_ctx->iter_cls,
                          key,
@@ -409,6 +427,8 @@ struct GetClosestContext
 {
   struct Value **values;
 
+  enum GNUNET_BLOCK_Type type;
+
   unsigned int num_results;
 
   const struct GNUNET_HashCode *key;
@@ -427,6 +447,9 @@ find_closest (void *cls,
   if (1 != GNUNET_CRYPTO_hash_cmp (key,
                                    gcc->key))
     return GNUNET_OK; /* useless */
+  if ( (val->type != gcc->type) &&
+       (GNUNET_BLOCK_TYPE_ANY != gcc->type) )
+    return GNUNET_OK; /* useless */
   j = gcc->num_results;
   for (unsigned int i = 0; i < gcc->num_results; i++)
   {
@@ -457,6 +480,7 @@ find_closest (void *cls,
  *
  * @param cls closure (internal context for the plugin)
  * @param key area of the keyspace to look into
+ * @param type desired block type for the replies
  * @param num_results number of results that should be returned to @a iter
  * @param iter maybe NULL (to just count)
  * @param iter_cls closure for @a iter
@@ -465,6 +489,7 @@ find_closest (void *cls,
 static unsigned int
 heap_plugin_get_closest (void *cls,
                          const struct GNUNET_HashCode *key,
+                         enum GNUNET_BLOCK_Type type,
                          unsigned int num_results,
                          GNUNET_DATACACHE_Iterator iter,
                          void *iter_cls)
@@ -473,6 +498,7 @@ heap_plugin_get_closest (void *cls,
   struct Value *values[num_results];
   struct GetClosestContext gcc = {
     .values = values,
+    .type = type,
     .num_results = num_results,
     .key = key
   };
diff --git a/src/datacache/plugin_datacache_postgres.c 
b/src/datacache/plugin_datacache_postgres.c
index 6a44c44a5..61b699c8d 100644
--- a/src/datacache/plugin_datacache_postgres.c
+++ b/src/datacache/plugin_datacache_postgres.c
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet
-     Copyright (C) 2006, 2009, 2010, 2012, 2015, 2017, 2018 GNUnet e.V.
+     Copyright (C) 2006, 2009, 2010, 2012, 2015, 2017, 2018, 2022 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
@@ -109,9 +109,13 @@ init_connection (struct Plugin *plugin)
                             " ORDER BY prox ASC, discard_time ASC LIMIT 1",
                             0),
     GNUNET_PQ_make_prepare ("get_closest",
-                            "SELECT discard_time,type,value,path,key FROM 
gn011dc "
-                            "WHERE key>=$1 AND discard_time >= $2 ORDER BY key 
ASC LIMIT $3",
-                            3),
+                            "SELECT discard_time,type,value,path,key FROM 
gn011dc"
+                            " WHERE key >= $1"
+                            "   AND discard_time >= $2"
+                            "   AND ( (type = $3) OR ( 0 = $3) )"
+                            " ORDER BY key ASC"
+                            " LIMIT $4",
+                            4),
     GNUNET_PQ_make_prepare ("delrow",
                             "DELETE FROM gn011dc WHERE oid=$1",
                             1),
@@ -511,6 +515,7 @@ extract_result_cb (void *cls,
  *
  * @param cls closure (internal context for the plugin)
  * @param key area of the keyspace to look into
+ * @param type desired block type for the replies
  * @param num_results number of results that should be returned to @a iter
  * @param iter maybe NULL (to just count)
  * @param iter_cls closure for @a iter
@@ -519,16 +524,19 @@ extract_result_cb (void *cls,
 static unsigned int
 postgres_plugin_get_closest (void *cls,
                              const struct GNUNET_HashCode *key,
+                             enum GNUNET_BLOCK_Type type,
                              unsigned int num_results,
                              GNUNET_DATACACHE_Iterator iter,
                              void *iter_cls)
 {
   struct Plugin *plugin = cls;
   uint32_t num_results32 = (uint32_t) num_results;
+  uint32_t type32 = (uint32_t) type;
   struct GNUNET_TIME_Absolute now;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (key),
     GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_uint32 (&type32),
     GNUNET_PQ_query_param_uint32 (&num_results32),
     GNUNET_PQ_query_param_end
   };
diff --git a/src/datacache/plugin_datacache_sqlite.c 
b/src/datacache/plugin_datacache_sqlite.c
index d08b32caf..46ff66dce 100644
--- a/src/datacache/plugin_datacache_sqlite.c
+++ b/src/datacache/plugin_datacache_sqlite.c
@@ -454,6 +454,7 @@ sqlite_plugin_del (void *cls)
  *
  * @param cls closure (internal context for the plugin)
  * @param key area of the keyspace to look into
+ * @param type desired block type for the replies
  * @param num_results number of results that should be returned to @a iter
  * @param iter maybe NULL (to just count)
  * @param iter_cls closure for @a iter
@@ -462,11 +463,13 @@ sqlite_plugin_del (void *cls)
 static unsigned int
 sqlite_plugin_get_closest (void *cls,
                            const struct GNUNET_HashCode *key,
+                           enum GNUNET_BLOCK_Type type,
                            unsigned int num_results,
                            GNUNET_DATACACHE_Iterator iter,
                            void *iter_cls)
 {
   struct Plugin *plugin = cls;
+  uint32_t type32 = type;
   uint32_t num_results32 = num_results;
   struct GNUNET_TIME_Absolute now;
   struct GNUNET_TIME_Absolute exp;
@@ -474,38 +477,46 @@ sqlite_plugin_get_closest (void *cls,
   void *dat;
   unsigned int cnt;
   size_t psize;
-  uint32_t type;
+  uint32_t rtype;
   struct GNUNET_HashCode hc;
   struct GNUNET_DHT_PathElement *path;
-  struct GNUNET_SQ_QueryParam params[] =
-  { GNUNET_SQ_query_param_auto_from_type (key),
+  struct GNUNET_SQ_QueryParam params[] = {
+    GNUNET_SQ_query_param_auto_from_type (key),
     GNUNET_SQ_query_param_absolute_time (&now),
+    GNUNET_SQ_query_param_uint32 (&type32),
     GNUNET_SQ_query_param_uint32 (&num_results32),
-    GNUNET_SQ_query_param_end };
-  struct GNUNET_SQ_ResultSpec rs[] =
-  { GNUNET_SQ_result_spec_variable_size (&dat, &size),
+    GNUNET_SQ_query_param_end
+  };
+  struct GNUNET_SQ_ResultSpec rs[] = {
+    GNUNET_SQ_result_spec_variable_size (&dat, &size),
     GNUNET_SQ_result_spec_absolute_time (&exp),
     GNUNET_SQ_result_spec_variable_size ((void **) &path, &psize),
-    GNUNET_SQ_result_spec_uint32 (&type),
+    GNUNET_SQ_result_spec_uint32 (&rtype),
     GNUNET_SQ_result_spec_auto_from_type (&hc),
-    GNUNET_SQ_result_spec_end };
+    GNUNET_SQ_result_spec_end
+  };
 
   now = GNUNET_TIME_absolute_get ();
   LOG (GNUNET_ERROR_TYPE_DEBUG,
        "Processing GET_CLOSEST for key `%s'\n",
        GNUNET_h2s (key));
-  if (GNUNET_OK != GNUNET_SQ_bind (plugin->get_closest_stmt, params))
+  if (GNUNET_OK !=
+      GNUNET_SQ_bind (plugin->get_closest_stmt,
+                      params))
   {
     LOG_SQLITE (plugin->dbh,
                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
                 "sqlite3_bind_xxx");
-    GNUNET_SQ_reset (plugin->dbh, plugin->get_closest_stmt);
+    GNUNET_SQ_reset (plugin->dbh,
+                     plugin->get_closest_stmt);
     return 0;
   }
   cnt = 0;
   while (SQLITE_ROW == sqlite3_step (plugin->get_closest_stmt))
   {
-    if (GNUNET_OK != GNUNET_SQ_extract_result (plugin->get_closest_stmt, rs))
+    if (GNUNET_OK !=
+        GNUNET_SQ_extract_result (plugin->get_closest_stmt,
+                                  rs))
     {
       GNUNET_break (0);
       break;
@@ -522,14 +533,22 @@ sqlite_plugin_get_closest (void *cls,
          "Found %u-byte result at %s when processing GET_CLOSE\n",
          (unsigned int) size,
          GNUNET_h2s (&hc));
-    if (GNUNET_OK != iter (iter_cls, &hc, size, dat, type, exp, psize, path))
+    if (GNUNET_OK != iter (iter_cls,
+                           &hc,
+                           size,
+                           dat,
+                           rtype,
+                           exp,
+                           psize,
+                           path))
     {
       GNUNET_SQ_cleanup_result (rs);
       break;
     }
     GNUNET_SQ_cleanup_result (rs);
   }
-  GNUNET_SQ_reset (plugin->dbh, plugin->get_closest_stmt);
+  GNUNET_SQ_reset (plugin->dbh,
+                   plugin->get_closest_stmt);
   return cnt;
 }
 
@@ -620,7 +639,7 @@ libgnunet_plugin_datacache_sqlite_init (void *cls)
                    &plugin->get_stmt)) ||
       (SQLITE_OK != sq_prepare (plugin->dbh,
                                 "SELECT _ROWID_,key,value FROM ds091"
-                                " WHERE expire < ?"
+                                " WHERE expire < ?1"
                                 " ORDER BY expire ASC LIMIT 1",
                                 &plugin->del_expired_stmt)) ||
       (SQLITE_OK != sq_prepare (plugin->dbh,
@@ -633,7 +652,10 @@ libgnunet_plugin_datacache_sqlite_init (void *cls)
       (SQLITE_OK !=
        sq_prepare (plugin->dbh,
                    "SELECT value,expire,path,type,key FROM ds091 "
-                   "WHERE key>=? AND expire >= ? ORDER BY KEY ASC LIMIT ?",
+                   " WHERE key>=?1 "
+                   "  AND expire >= ?2"
+                   "  AND ( (type=?3) or (0 == ?3) )"
+                   " ORDER BY KEY ASC LIMIT ?4",
                    &plugin->get_closest_stmt)))
   {
     LOG_SQLITE (plugin->dbh,
diff --git a/src/datacache/plugin_datacache_template.c 
b/src/datacache/plugin_datacache_template.c
index 231413ce9..2f7b41dbe 100644
--- a/src/datacache/plugin_datacache_template.c
+++ b/src/datacache/plugin_datacache_template.c
@@ -116,6 +116,7 @@ template_plugin_del (void *cls)
  *
  * @param cls closure (internal context for the plugin)
  * @param key area of the keyspace to look into
+ * @param type desired block type for the replies
  * @param num_results number of results that should be returned to @a iter
  * @param iter maybe NULL (to just count)
  * @param iter_cls closure for @a iter
@@ -124,6 +125,7 @@ template_plugin_del (void *cls)
 static unsigned int
 template_plugin_get_closest (void *cls,
                              const struct GNUNET_HashCode *key,
+                             enum GNUNET_BLOCK_Type type,
                              unsigned int num_results,
                              GNUNET_DATACACHE_Iterator iter,
                              void *iter_cls)
diff --git a/src/dht/.gitignore b/src/dht/.gitignore
index 25b1daf28..bd8af1217 100644
--- a/src/dht/.gitignore
+++ b/src/dht/.gitignore
@@ -10,3 +10,4 @@ test_dht_monitor
 test_dht_multipeer
 test_dht_tools.py
 test_dht_twopeer
+gnunet-dht-hello
diff --git a/src/dht/Makefile.am b/src/dht/Makefile.am
index be48fab02..6d1090901 100644
--- a/src/dht/Makefile.am
+++ b/src/dht/Makefile.am
@@ -50,7 +50,8 @@ libexec_PROGRAMS = \
 bin_PROGRAMS = \
  gnunet-dht-monitor \
  gnunet-dht-get \
- gnunet-dht-put
+ gnunet-dht-put \
+ gnunet-dht-hello
 
 noinst_PROGRAMS = \
  gnunet-dht-profiler
@@ -58,8 +59,6 @@ noinst_PROGRAMS = \
 gnunet_service_dht_SOURCES = \
  gnunet-service-dht.c gnunet-service-dht.h \
  gnunet-service-dht_datacache.c gnunet-service-dht_datacache.h \
- gnunet-service-dht_hello.c gnunet-service-dht_hello.h \
- gnunet-service-dht_nse.c gnunet-service-dht_nse.h \
  gnunet-service-dht_neighbours.c gnunet-service-dht_neighbours.h \
  gnunet-service-dht_routing.c gnunet-service-dht_routing.h
 gnunet_service_dht_LDADD = \
@@ -82,7 +81,14 @@ gnunet_dht_get_SOURCES = \
  gnunet-dht-get.c
 gnunet_dht_get_LDADD = \
   libgnunetdht.la \
-  $(top_builddir)/src/core/libgnunetcore.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+gnunet_dht_get_LDFLAGS = \
+  $(GN_LIBINTL)
+
+gnunet_dht_hello_SOURCES = \
+ gnunet-dht-hello.c
+gnunet_dht_hello_LDADD = \
+  libgnunetdht.la \
   $(top_builddir)/src/util/libgnunetutil.la
 gnunet_dht_get_LDFLAGS = \
   $(GN_LIBINTL)
@@ -91,7 +97,6 @@ gnunet_dht_put_SOURCES = \
  gnunet-dht-put.c
 gnunet_dht_put_LDADD = \
   libgnunetdht.la \
-  $(top_builddir)/src/core/libgnunetcore.la \
   $(top_builddir)/src/util/libgnunetutil.la
 gnunet_dht_put_LDFLAGS = \
   $(GN_LIBINTL)
@@ -100,7 +105,6 @@ gnunet_dht_monitor_SOURCES = \
  gnunet-dht-monitor.c
 gnunet_dht_monitor_LDADD = \
   libgnunetdht.la \
-  $(top_builddir)/src/core/libgnunetcore.la \
   $(top_builddir)/src/util/libgnunetutil.la
 gnunet_dht_monitor_LDFLAGS = \
   $(GN_LIBINTL)
diff --git a/src/dht/dht_api.c b/src/dht/dht_api.c
index 8389bbb95..cae8de726 100644
--- a/src/dht/dht_api.c
+++ b/src/dht/dht_api.c
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet.
-     Copyright (C) 2009, 2010, 2011, 2012, 2016, 2018 GNUnet e.V.
+     Copyright (C) 2009-2012, 2016, 2018, 2022 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
@@ -196,6 +196,40 @@ struct GNUNET_DHT_MonitorHandle
 };
 
 
+/**
+ * Handle to get a HELLO URL from the DHT for manual bootstrapping.
+ */
+struct GNUNET_DHT_HelloGetHandle
+{
+
+  /**
+   * DLL.
+   */
+  struct GNUNET_DHT_HelloGetHandle *next;
+
+  /**
+   * DLL.
+   */
+  struct GNUNET_DHT_HelloGetHandle *prev;
+
+  /**
+   * Function to call with the result.
+   */
+  GNUNET_DHT_HelloGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Connection to the DHT service.
+   */
+  struct GNUNET_DHT_Handle *dht_handle;
+
+};
+
+
 /**
  * Connection to the DHT service.
  */
@@ -231,6 +265,16 @@ struct GNUNET_DHT_Handle
    */
   struct GNUNET_DHT_PutHandle *put_tail;
 
+  /**
+   * DLL.
+   */
+  struct GNUNET_DHT_HelloGetHandle *hgh_head;
+
+  /**
+   * DLL.
+   */
+  struct GNUNET_DHT_HelloGetHandle *hgh_tail;
+
   /**
    * Hash map containing the current outstanding unique GET requests
    * (values are of type `struct GNUNET_DHT_GetHandle`).
@@ -726,6 +770,7 @@ process_client_result (void *cls,
   const struct GNUNET_DHT_ClientResultMessage *crm = cls;
   struct GNUNET_DHT_GetHandle *get_handle = value;
   size_t msize = ntohs (crm->header.size) - sizeof(*crm);
+  uint16_t type = ntohl (crm->type);
   uint32_t put_path_length = ntohl (crm->put_path_length);
   uint32_t get_path_length = ntohl (crm->get_path_length);
   const struct GNUNET_DHT_PathElement *put_path;
@@ -745,7 +790,13 @@ process_client_result (void *cls,
          (unsigned long long) get_handle->unique_id);
     return GNUNET_YES;
   }
-  /* FIXME: might want to check that type matches */
+  if ( (get_handle->type != GNUNET_BLOCK_TYPE_ANY) &&
+       (get_handle->type != type) )
+  {
+    /* type mismatch */
+    GNUNET_break (0);
+    return GNUNET_YES;
+  }
   meta_length =
     sizeof(struct GNUNET_DHT_PathElement) * (get_path_length + 
put_path_length);
   data_length = msize - meta_length;
@@ -786,7 +837,7 @@ process_client_result (void *cls,
                     get_path_length,
                     put_path,
                     put_path_length,
-                    ntohl (crm->type),
+                    type,
                     data_length,
                     data);
   return GNUNET_YES;
@@ -812,6 +863,53 @@ handle_client_result (void *cls,
 }
 
 
+/**
+ * Process a client HELLO message received from the service.
+ *
+ * @param cls The DHT handle.
+ * @param hdr HELLO URL message from the service.
+ * @return #GNUNET_OK if @a hdr is well-formed
+ */
+static enum GNUNET_GenericReturnValue
+check_client_hello (void *cls,
+                    const struct GNUNET_MessageHeader *hdr)
+{
+  uint16_t len = ntohs (hdr->size);
+  const char *buf = (const char *) &hdr[1];
+
+  (void) cls;
+  if ('\0' != buf[len - sizeof (*hdr) - 1])
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Process a client HELLO message received from the service.
+ *
+ * @param cls The DHT handle.
+ * @param hdr HELLO URL message from the service.
+ */
+static void
+handle_client_hello (void *cls,
+                     const struct GNUNET_MessageHeader *hdr)
+{
+  struct GNUNET_DHT_Handle *handle = cls;
+  const char *url = (const char *) &hdr[1];
+  struct GNUNET_DHT_HelloGetHandle *hgh;
+
+  while (NULL != (hgh = handle->hgh_head))
+  {
+    hgh->cb (hgh->cb_cls,
+             url);
+    GNUNET_DHT_hello_get_cancel (hgh);
+  }
+}
+
+
 /**
  * Process a MQ PUT transmission notification.
  *
@@ -859,6 +957,10 @@ try_connect (struct GNUNET_DHT_Handle *h)
                            GNUNET_MESSAGE_TYPE_DHT_CLIENT_RESULT,
                            struct GNUNET_DHT_ClientResultMessage,
                            h),
+    GNUNET_MQ_hd_var_size (client_hello,
+                           GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_URL,
+                           struct GNUNET_MessageHeader,
+                           h),
     GNUNET_MQ_handler_end ()
   };
 
@@ -1250,4 +1352,64 @@ GNUNET_DHT_verify_path (const struct GNUNET_HashCode 
*key,
 }
 
 
+struct GNUNET_DHT_HelloGetHandle *
+GNUNET_DHT_hello_get (struct GNUNET_DHT_Handle *dht_handle,
+                      GNUNET_DHT_HelloGetCallback cb,
+                      void *cb_cls)
+{
+  struct GNUNET_DHT_HelloGetHandle *hgh;
+  struct GNUNET_MessageHeader *hdr;
+  struct GNUNET_MQ_Envelope *env;
+
+  hgh = GNUNET_new (struct GNUNET_DHT_HelloGetHandle);
+  hgh->cb = cb;
+  hgh->cb_cls = cb_cls;
+  hgh->dht_handle = dht_handle;
+  GNUNET_CONTAINER_DLL_insert (dht_handle->hgh_head,
+                               dht_handle->hgh_tail,
+                               hgh);
+  env = GNUNET_MQ_msg (hdr,
+                       GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_GET);
+  GNUNET_MQ_send (dht_handle->mq,
+                  env);
+  return hgh;
+}
+
+
+void
+GNUNET_DHT_hello_get_cancel (struct GNUNET_DHT_HelloGetHandle *hgh)
+{
+  struct GNUNET_DHT_Handle *dht_handle = hgh->dht_handle;
+
+  GNUNET_CONTAINER_DLL_remove (dht_handle->hgh_head,
+                               dht_handle->hgh_tail,
+                               hgh);
+  GNUNET_free (hgh);
+}
+
+
+void
+GNUNET_DHT_hello_offer (struct GNUNET_DHT_Handle *dht_handle,
+                        const char *url,
+                        GNUNET_SCHEDULER_TaskCallback cb,
+                        void *cb_cls)
+{
+  struct GNUNET_MessageHeader *hdr;
+  size_t slen = strlen (url) + 1;
+  struct GNUNET_MQ_Envelope *env;
+
+  env = GNUNET_MQ_msg_extra (hdr,
+                             slen,
+                             GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_URL);
+  memcpy (&hdr[1],
+          url,
+          slen);
+  GNUNET_MQ_notify_sent (env,
+                         cb,
+                         cb_cls);
+  GNUNET_MQ_send (dht_handle->mq,
+                  env);
+}
+
+
 /* end of dht_api.c */
diff --git a/src/dht/gnunet-dht-get.c b/src/dht/gnunet-dht-get.c
index f1076490b..42ffe75ba 100644
--- a/src/dht/gnunet-dht-get.c
+++ b/src/dht/gnunet-dht-get.c
@@ -57,6 +57,11 @@ static unsigned int verbose;
  */
 static int demultixplex_everywhere;
 
+/**
+ * Use #GNUNET_DHT_RO_RECORD_ROUTE.
+ */
+static int record_route;
+
 /**
  * Handle to the DHT
  */
@@ -160,9 +165,9 @@ get_result_iterator (void *cls,
            : _ ("Result %d, type %d:\n"),
            result_count,
            type,
-           (unsigned int) size,
+           (int) size,
            (char *) data);
-  if (verbose)
+  if (record_route && verbose)
   {
     fprintf (stdout,
              "  GET path: ");
@@ -200,6 +205,7 @@ run (void *cls,
      const struct GNUNET_CONFIGURATION_Handle *c)
 {
   struct GNUNET_HashCode key;
+  enum GNUNET_DHT_RouteOption ro;
 
   cfg = c;
   if (NULL == query_key)
@@ -228,13 +234,16 @@ run (void *cls,
              GNUNET_h2s_full (&key));
   GNUNET_SCHEDULER_add_shutdown (&cleanup_task, NULL);
   tt = GNUNET_SCHEDULER_add_delayed (timeout_request, &timeout_task, NULL);
+  ro = GNUNET_DHT_RO_NONE;
+  if (demultixplex_everywhere)
+    ro |= GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE;
+  if (record_route)
+    ro |= GNUNET_DHT_RO_RECORD_ROUTE;
   get_handle = GNUNET_DHT_get_start (dht_handle,
                                      query_type,
                                      &key,
                                      replication,
-                                     (demultixplex_everywhere)
-                                     ? GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE
-                                     : GNUNET_DHT_RO_NONE,
+                                     ro,
                                      NULL,
                                      0,
                                      &get_result_iterator,
@@ -265,6 +274,11 @@ main (int argc, char *const *argv)
       "LEVEL",
       gettext_noop ("how many parallel requests (replicas) to create"),
       &replication),
+    GNUNET_GETOPT_option_flag (
+      'R',
+      "record",
+      gettext_noop ("use DHT's record route option"),
+      &record_route),
     GNUNET_GETOPT_option_uint (
       't',
       "type",
diff --git a/src/dht/gnunet-dht-hello.c b/src/dht/gnunet-dht-hello.c
new file mode 100644
index 000000000..369ed5643
--- /dev/null
+++ b/src/dht/gnunet-dht-hello.c
@@ -0,0 +1,178 @@
+/*
+     This file is part of GNUnet.
+     Copyright (C) 2022 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
+ */
+/**
+ * @file dht/gnunet-dht-hello.c
+ * @brief Obtain HELLO from DHT for bootstrapping
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "gnunet_dht_service.h"
+
+#define LOG(kind, ...) GNUNET_log_from (kind, "dht-clients", __VA_ARGS__)
+
+/**
+ * Handle to the DHT
+ */
+static struct GNUNET_DHT_Handle *dht_handle;
+
+/**
+ * Handle to the DHT hello get operation.
+ */
+static struct GNUNET_DHT_HelloGetHandle *get_hello_handle;
+
+/**
+ * Global status value
+ */
+static int global_ret;
+
+
+/**
+ * Task run to clean up on shutdown.
+ *
+ * @param cls unused
+ */
+static void
+cleanup_task (void *cls)
+{
+  if (NULL != get_hello_handle)
+  {
+    GNUNET_DHT_hello_get_cancel (get_hello_handle);
+    get_hello_handle = NULL;
+  }
+  if (NULL != dht_handle)
+  {
+    GNUNET_DHT_disconnect (dht_handle);
+    dht_handle = NULL;
+  }
+}
+
+
+/**
+ * Task run when we are finished. Triggers shutdown.
+ *
+ * @param cls unused
+ */
+static void
+hello_done_cb (void *cls)
+{
+  GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Function called on our HELLO.
+ *
+ * @param cls closure
+ * @param url the HELLO URL
+ */
+static void
+hello_result_cb (void *cls,
+                 const char *url)
+{
+  get_hello_handle = NULL;
+  fprintf (stdout,
+           "%s\n",
+           url);
+  GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be 
NULL!)
+ * @param cfg configuration
+ */
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  (void) cls;
+  (void) cfgfile;
+  GNUNET_SCHEDULER_add_shutdown (&cleanup_task,
+                                 NULL);
+  if (NULL == (dht_handle = GNUNET_DHT_connect (cfg,
+                                                1)))
+  {
+    fprintf (stderr,
+             _ ("Failed to connect to DHT service!\n"));
+    global_ret = EXIT_NOTCONFIGURED;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (NULL == args[0])
+  {
+    get_hello_handle = GNUNET_DHT_hello_get (dht_handle,
+                                             &hello_result_cb,
+                                             NULL);
+    GNUNET_break (NULL != get_hello_handle);
+  }
+  else
+  {
+    GNUNET_DHT_hello_offer (dht_handle,
+                            args[0],
+                            &hello_done_cb,
+                            NULL);
+  }
+}
+
+
+/**
+ * Entry point for gnunet-dht-hello
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+      char *const *argv)
+{
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_OPTION_END
+  };
+  enum GNUNET_GenericReturnValue iret;
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_get_utf8_args (argc, argv,
+                                    &argc, &argv))
+    return 2;
+  iret = GNUNET_PROGRAM_run (
+    argc,
+    argv,
+    "gnunet-dht-hello [URL]",
+    gettext_noop (
+      "Obtain HELLO from DHT or provide HELLO to DHT for bootstrapping"),
+    options,
+    &run,
+    NULL);
+  if (GNUNET_SYSERR == iret)
+    return EXIT_FAILURE;
+  if (GNUNET_NO == iret)
+    return EXIT_SUCCESS;
+  return global_ret;
+}
+
+
+/* end of gnunet-dht-hello.c */
diff --git a/src/dht/gnunet-service-dht.c b/src/dht/gnunet-service-dht.c
index da46dcfee..39433791d 100644
--- a/src/dht/gnunet-service-dht.c
+++ b/src/dht/gnunet-service-dht.c
@@ -27,53 +27,335 @@
 #include "platform.h"
 #include "gnunet_block_lib.h"
 #include "gnunet_util_lib.h"
-#include "gnunet_transport_service.h"
-#include "gnunet_transport_hello_service.h"
 #include "gnunet_hello_lib.h"
+#include "gnunet_hello_uri_lib.h"
 #include "gnunet_dht_service.h"
 #include "gnunet_statistics_service.h"
 #include "gnunet-service-dht.h"
 #include "gnunet-service-dht_datacache.h"
-#include "gnunet-service-dht_hello.h"
 #include "gnunet-service-dht_neighbours.h"
-#include "gnunet-service-dht_nse.h"
 #include "gnunet-service-dht_routing.h"
 
+/**
+ * How often do we broadcast our HELLO to neighbours if
+ * nothing special happens?
+ */
+#define HELLO_FREQUENCY GNUNET_TIME_UNIT_HOURS
+
+
+/**
+ * Information we keep per underlay.
+ */
+struct GDS_Underlay
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct GDS_Underlay *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct GDS_Underlay *prev;
+
+  /**
+   * Environment for this underlay.
+   */
+  struct GNUNET_DHTU_PluginEnvironment env;
+
+  /**
+   * Underlay API handle.
+   */
+  struct GNUNET_DHTU_PluginFunctions *dhtu;
+
+  /**
+   * current network size estimate for this underlay.
+   */
+  double network_size_estimate;
+
+  /**
+   * Name of the underlay (i.e. "gnunet" or "ip").
+   */
+  char *name;
+
+  /**
+   * Name of the library providing the underlay.
+   */
+  char *libname;
+};
+
+
+/**
+ * An address of this peer.
+ */
+struct MyAddress
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct MyAddress *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct MyAddress *prev;
+
+  /**
+   * Underlay handle for the address.
+   */
+  struct GNUNET_DHTU_Source *source;
+
+  /**
+   * Textual representation of the address.
+   */
+  char *url;
+
+  /**
+   * Underlay of this address.
+   */
+  struct GDS_Underlay *u;
+};
+
+
 /**
  * Our HELLO
  */
-struct GNUNET_MessageHeader *GDS_my_hello;
+struct GNUNET_HELLO_Builder *GDS_my_hello;
 
 /**
- * Handle to get our current HELLO.
+ * Identity of this peer.
  */
-static struct GNUNET_TRANSPORT_HelloGetHandle *ghh;
+struct GNUNET_PeerIdentity GDS_my_identity;
 
 /**
- * Hello address expiration
+ * Hash of the identity of this peer.
  */
-struct GNUNET_TIME_Relative hello_expiration;
+struct GNUNET_HashCode GDS_my_identity_hash;
+
+/**
+ * Our private key.
+ */
+struct GNUNET_CRYPTO_EddsaPrivateKey GDS_my_private_key;
+
+/**
+ * Task broadcasting our HELLO.
+ */
+static struct GNUNET_SCHEDULER_Task *hello_task;
+
+/**
+ * Handles for the DHT underlays.
+ */
+static struct GDS_Underlay *u_head;
+
+/**
+ * Handles for the DHT underlays.
+ */
+static struct GDS_Underlay *u_tail;
+
+/**
+ * Head of addresses of this peer.
+ */
+static struct MyAddress *a_head;
+
+/**
+ * Tail of addresses of this peer.
+ */
+static struct MyAddress *a_tail;
+
+/**
+ * log of the current network size estimate, used as the point where
+ * we switch between random and deterministic routing.
+ */
+static double log_of_network_size_estimate;
+
+
+/**
+ * Callback that is called when network size estimate is updated.
+ *
+ * @param cls a `struct GDS_Underlay`
+ * @param timestamp time when the estimate was received from the server (or 
created by the server)
+ * @param logestimate the log(Base 2) value of the current network size 
estimate
+ * @param std_dev standard deviation for the estimate
+ *
+ */
+static void
+update_network_size_estimate (void *cls,
+                              struct GNUNET_TIME_Absolute timestamp,
+                              double logestimate,
+                              double std_dev)
+{
+  struct GDS_Underlay *u = cls;
+  double sum = 0.0;
+
+  GNUNET_STATISTICS_update (GDS_stats,
+                            "# Network size estimates received",
+                            1,
+                            GNUNET_NO);
+  /* do not allow estimates < 0.5 */
+  u->network_size_estimate = pow (2.0,
+                                  GNUNET_MAX (0.5,
+                                              logestimate));
+  for (struct GDS_Underlay *p = u_head; NULL != p; p = p->next)
+    sum += p->network_size_estimate;
+  if (sum <= 2.0)
+    log_of_network_size_estimate = 0.5;
+  else
+    log_of_network_size_estimate = log2 (sum);
+}
+
+
+/**
+ * Return the current NSE
+ *
+ * @return the current NSE as a logarithm
+ */
+double
+GDS_NSE_get (void)
+{
+  return log_of_network_size_estimate;
+}
 
 
 #include "gnunet-service-dht_clients.c"
 
 
 /**
- * Receive the HELLO from transport service, free current and replace
- * if necessary.
+ * Task run periodically to broadcast our HELLO.
  *
  * @param cls NULL
- * @param message HELLO message of peer
  */
 static void
-process_hello (void *cls,
-               const struct GNUNET_MessageHeader *message)
+broadcast_hello (void *cls)
 {
-  GNUNET_free (GDS_my_hello);
-  GDS_my_hello = GNUNET_malloc (ntohs (message->size));
-  GNUNET_memcpy (GDS_my_hello,
-                 message,
-                 ntohs (message->size));
+  struct GNUNET_MessageHeader *hello;
+
+  (void) cls;
+  /* TODO: randomize! */
+  hello_task = GNUNET_SCHEDULER_add_delayed (HELLO_FREQUENCY,
+                                             &broadcast_hello,
+                                             NULL);
+  hello = GNUNET_HELLO_builder_to_dht_hello_msg (GDS_my_hello,
+                                                 &GDS_my_private_key);
+  if (NULL == hello)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  GDS_NEIGHBOURS_broadcast (hello);
+  GNUNET_free (hello);
+}
+
+
+/**
+ * Function to call with new addresses of this peer.
+ *
+ * @param cls the closure
+ * @param address address under which we are likely reachable,
+ *           pointer will remain valid until @e address_del_cb is called; to 
be used for HELLOs. Example: "ip+udp://$PID/1.1.1.1:2086/"
+ * @param source handle for sending from this address, NULL if we can only 
receive
+ * @param[out] ctx storage space for DHT to use in association with this 
address
+ */
+static void
+u_address_add (void *cls,
+               const char *address,
+               struct GNUNET_DHTU_Source *source,
+               void **ctx)
+{
+  struct GDS_Underlay *u = cls;
+  struct MyAddress *a;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Underlay adds address %s for this peer\n",
+              address);
+  a = GNUNET_new (struct MyAddress);
+  a->source = source;
+  a->url = GNUNET_strdup (address);
+  a->u = u;
+  GNUNET_CONTAINER_DLL_insert (a_head,
+                               a_tail,
+                               a);
+  *ctx = a;
+  GNUNET_HELLO_builder_add_address (GDS_my_hello,
+                                    address);
+  if (NULL != hello_task)
+    GNUNET_SCHEDULER_cancel (hello_task);
+  hello_task = GNUNET_SCHEDULER_add_now (&broadcast_hello,
+                                         NULL);
+}
+
+
+/**
+ * Function to call with expired addresses of this peer.
+ *
+ * @param[in] ctx storage space used by the DHT in association with this 
address
+ */
+static void
+u_address_del (void *ctx)
+{
+  struct MyAddress *a = ctx;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Underlay deletes address %s for this peer\n",
+              a->url);
+  GNUNET_HELLO_builder_del_address (GDS_my_hello,
+                                    a->url);
+  GNUNET_CONTAINER_DLL_remove (a_head,
+                               a_tail,
+                               a);
+  GNUNET_free (a->url);
+  GNUNET_free (a);
+  if (NULL != hello_task)
+    GNUNET_SCHEDULER_cancel (hello_task);
+  hello_task = GNUNET_SCHEDULER_add_now (&broadcast_hello,
+                                         NULL);
+}
+
+
+void
+GDS_u_try_connect (const struct GNUNET_PeerIdentity *pid,
+                   const char *address)
+{
+  for (struct GDS_Underlay *u = u_head;
+       NULL != u;
+       u = u->next)
+    u->dhtu->try_connect (u->dhtu->cls,
+                          pid,
+                          address);
+}
+
+
+void
+GDS_u_send (struct GDS_Underlay *u,
+            struct GNUNET_DHTU_Target *target,
+            const void *msg,
+            size_t msg_size,
+            GNUNET_SCHEDULER_TaskCallback finished_cb,
+            void *finished_cb_cls)
+{
+  u->dhtu->send (u->dhtu->cls,
+                 target,
+                 msg,
+                 msg_size,
+                 finished_cb,
+                 finished_cb_cls);
+}
+
+
+void
+GDS_u_drop (struct GDS_Underlay *u,
+            struct GNUNET_DHTU_PreferenceHandle *ph)
+{
+  u->dhtu->drop (ph);
+}
+
+
+struct GNUNET_DHTU_PreferenceHandle *
+GDS_u_hold (struct GDS_Underlay *u,
+            struct GNUNET_DHTU_Target *target)
+{
+  return u->dhtu->hold (u->dhtu->cls,
+                        target);
 }
 
 
@@ -85,30 +367,97 @@ process_hello (void *cls,
 static void
 shutdown_task (void *cls)
 {
-  if (NULL != ghh)
+  struct GDS_Underlay *u;
+
+  while (NULL != (u = u_head))
   {
-    GNUNET_TRANSPORT_hello_get_cancel (ghh);
-    ghh = NULL;
+    GNUNET_PLUGIN_unload (u->libname,
+                          u->dhtu);
+    GNUNET_CONTAINER_DLL_remove (u_head,
+                                 u_tail,
+                                 u);
+    GNUNET_free (u->name);
+    GNUNET_free (u->libname);
+    GNUNET_free (u);
   }
   GDS_NEIGHBOURS_done ();
   GDS_DATACACHE_done ();
   GDS_ROUTING_done ();
-  GDS_HELLO_done ();
-  GDS_NSE_done ();
   if (NULL != GDS_block_context)
   {
     GNUNET_BLOCK_context_destroy (GDS_block_context);
     GDS_block_context = NULL;
   }
+  GDS_CLIENTS_stop ();
   if (NULL != GDS_stats)
   {
     GNUNET_STATISTICS_destroy (GDS_stats,
                                GNUNET_YES);
     GDS_stats = NULL;
   }
-  GNUNET_free (GDS_my_hello);
-  GDS_my_hello = NULL;
-  GDS_CLIENTS_stop ();
+  if (NULL != GDS_my_hello)
+  {
+    GNUNET_HELLO_builder_free (GDS_my_hello);
+    GDS_my_hello = NULL;
+  }
+  if (NULL != hello_task)
+  {
+    GNUNET_SCHEDULER_cancel (hello_task);
+    hello_task = NULL;
+  }
+}
+
+
+/**
+ * Function iterating over all configuration sections.
+ * Loads plugins for enabled DHT underlays.
+ *
+ * @param cls NULL
+ * @param section configuration section to inspect
+ */
+static void
+load_underlay (void *cls,
+               const char *section)
+{
+  struct GDS_Underlay *u;
+  char *libname;
+
+  (void) cls;
+  if (0 != strncasecmp (section,
+                        "dhtu-",
+                        strlen ("dhtu-")))
+    return;
+  if (GNUNET_YES !=
+      GNUNET_CONFIGURATION_get_value_yesno (GDS_cfg,
+                                            section,
+                                            "ENABLED"))
+    return;
+  section += strlen ("dhtu-");
+  u = GNUNET_new (struct GDS_Underlay);
+  u->env.cls = u;
+  u->env.cfg = GDS_cfg;
+  u->env.address_add_cb = &u_address_add;
+  u->env.address_del_cb = &u_address_del;
+  u->env.network_size_cb = &update_network_size_estimate;
+  u->env.connect_cb = &GDS_u_connect;
+  u->env.disconnect_cb = &GDS_u_disconnect;
+  u->env.receive_cb = &GDS_u_receive;
+  GNUNET_asprintf (&libname,
+                   "libgnunet_plugin_dhtu_%s",
+                   section);
+  u->dhtu = GNUNET_PLUGIN_load (libname,
+                                &u->env);
+  if (NULL == u->dhtu)
+  {
+    GNUNET_free (libname);
+    GNUNET_free (u);
+    return;
+  }
+  u->libname = libname;
+  u->name = GNUNET_strdup (section);
+  GNUNET_CONTAINER_DLL_insert (u_head,
+                               u_tail,
+                               u);
 }
 
 
@@ -126,34 +475,64 @@ run (void *cls,
 {
   GDS_cfg = c;
   GDS_service = service;
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_time (c,
-                                           "transport",
-                                           "HELLO_EXPIRATION",
-                                           &hello_expiration))
   {
-    hello_expiration = GNUNET_CONSTANTS_HELLO_ADDRESS_EXPIRATION;
+    char *keyfile;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_filename (GDS_cfg,
+                                                 "PEER",
+                                                 "PRIVATE_KEY",
+                                                 &keyfile))
+    {
+      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                                 "PEER",
+                                 "PRIVATE_KEY");
+      GNUNET_SCHEDULER_shutdown ();
+      return;
+    }
+    if (GNUNET_SYSERR ==
+        GNUNET_CRYPTO_eddsa_key_from_file (keyfile,
+                                           GNUNET_YES,
+                                           &GDS_my_private_key))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to setup peer's private key\n");
+      GNUNET_free (keyfile);
+      GNUNET_SCHEDULER_shutdown ();
+      return;
+    }
+    GNUNET_free (keyfile);
   }
+  GNUNET_CRYPTO_eddsa_key_get_public (&GDS_my_private_key,
+                                      &GDS_my_identity.public_key);
+  GDS_my_hello = GNUNET_HELLO_builder_new (&GDS_my_identity);
+  GNUNET_CRYPTO_hash (&GDS_my_identity,
+                      sizeof(struct GNUNET_PeerIdentity),
+                      &GDS_my_identity_hash);
   GDS_block_context = GNUNET_BLOCK_context_create (GDS_cfg);
   GDS_stats = GNUNET_STATISTICS_create ("dht",
                                         GDS_cfg);
-  GNUNET_SERVICE_suspend (GDS_service);
   GDS_CLIENTS_init ();
   GDS_ROUTING_init ();
-  GDS_NSE_init ();
   GDS_DATACACHE_init ();
-  GDS_HELLO_init ();
-  if (GNUNET_OK != GDS_NEIGHBOURS_init ())
+  GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+                                 NULL);
+  if (GNUNET_OK !=
+      GDS_NEIGHBOURS_init ())
   {
-    shutdown_task (NULL);
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  GNUNET_CONFIGURATION_iterate_sections (GDS_cfg,
+                                         &load_underlay,
+                                         NULL);
+  if (NULL == u_head)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "No DHT underlays configured!\n");
+    GNUNET_SCHEDULER_shutdown ();
     return;
   }
-  GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
-                                 NULL);
-  ghh = GNUNET_TRANSPORT_hello_get (GDS_cfg,
-                                    GNUNET_TRANSPORT_AC_GLOBAL,
-                                    &process_hello,
-                                    NULL);
 }
 
 
diff --git a/src/dht/gnunet-service-dht.h b/src/dht/gnunet-service-dht.h
index 367ff426e..d3bb39455 100644
--- a/src/dht/gnunet-service-dht.h
+++ b/src/dht/gnunet-service-dht.h
@@ -27,12 +27,18 @@
 #define GNUNET_SERVICE_DHT_H
 
 #include "gnunet-service-dht_datacache.h"
+#include "gnunet-service-dht_neighbours.h"
 #include "gnunet_statistics_service.h"
 #include "gnunet_transport_service.h"
 
 
 #define DEBUG_DHT GNUNET_EXTRA_LOGGING
 
+/**
+ * Information we keep per underlay.
+ */
+struct GDS_Underlay;
+
 /**
  * Configuration we use.
  */
@@ -54,9 +60,81 @@ extern struct GNUNET_BLOCK_Context *GDS_block_context;
 extern struct GNUNET_STATISTICS_Handle *GDS_stats;
 
 /**
- * Our HELLO
+ * Our HELLO builder.
+ */
+extern struct GNUNET_HELLO_Builder *GDS_my_hello;
+
+/**
+ * Identity of this peer.
+ */
+extern struct GNUNET_PeerIdentity GDS_my_identity;
+
+/**
+ * Hash of the identity of this peer.
+ */
+extern struct GNUNET_HashCode GDS_my_identity_hash;
+
+/**
+ * Our private key.
+ */
+extern struct GNUNET_CRYPTO_EddsaPrivateKey GDS_my_private_key;
+
+
+/**
+ * Ask all underlays to connect to peer @a pid at @a address.
+ *
+ * @param pid identity of the peer we would connect to
+ * @param address an address of @a pid
  */
-extern struct GNUNET_MessageHeader *GDS_my_hello;
+void
+GDS_u_try_connect (const struct GNUNET_PeerIdentity *pid,
+                   const char *address);
+
+
+/**
+ * Send message to some other participant over the network.  Note that
+ * sending is not guaranteeing that the other peer actually received the
+ * message.  For any given @a target, the DHT must wait for the @a
+ * finished_cb to be called before calling send() again.
+ *
+ * @param u underlay to use for transmission
+ * @param target receiver identification
+ * @param msg message
+ * @param msg_size number of bytes in @a msg
+ * @param finished_cb function called once transmission is done
+ *        (not called if @a target disconnects, then only the
+ *         disconnect_cb is called).
+ * @param finished_cb_cls closure for @a finished_cb
+ */
+void
+GDS_u_send (struct GDS_Underlay *u,
+            struct GNUNET_DHTU_Target *target,
+            const void *msg,
+            size_t msg_size,
+            GNUNET_SCHEDULER_TaskCallback finished_cb,
+            void *finished_cb_cls);
+
+
+/**
+ * Drop a hold @a ph from underlay @a u.
+ *
+ * @param u the underlay controlling the hold
+ * @param ph the preference handle
+ */
+void
+GDS_u_drop (struct GDS_Underlay *u,
+            struct GNUNET_DHTU_PreferenceHandle *ph);
+
+
+/**
+ * Create a hold on @a target at underlay @a u.
+ *
+ * @param u the underlay controlling the target
+ * @param target the peer to hold the connection to
+ */
+struct GNUNET_DHTU_PreferenceHandle *
+GDS_u_hold (struct GDS_Underlay *u,
+            struct GNUNET_DHTU_Target *target);
 
 
 /**
@@ -128,4 +206,12 @@ GDS_CLIENTS_process_put (enum GNUNET_DHT_RouteOption 
options,
                          uint32_t hop_count,
                          uint32_t desired_replication_level);
 
+/**
+ * Return the current NSE
+ *
+ * @return the current NSE as a logarithm
+ */
+double
+GDS_NSE_get (void);
+
 #endif
diff --git a/src/dht/gnunet-service-dht_clients.c 
b/src/dht/gnunet-service-dht_clients.c
index a1c3024de..dc451ed15 100644
--- a/src/dht/gnunet-service-dht_clients.c
+++ b/src/dht/gnunet-service-dht_clients.c
@@ -562,6 +562,9 @@ handle_local_result (void *cls,
                      const struct GDS_DATACACHE_BlockData *bd)
 {
   /* FIXME: use 'cls' instead of looking up the client? */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Datacache provided result for query key %s\n",
+              GNUNET_h2s (&bd->key));
   GDS_CLIENTS_handle_reply (bd,
                             &bd->key,
                             0, NULL /* get_path */);
@@ -917,7 +920,7 @@ forward_reply (void *cls,
                               GNUNET_NO);
     return GNUNET_YES;          /* type mismatch */
   }
-  if ( (0 == (record->msg_options & GNUNET_DHT_RO_FIND_PEER)) &&
+  if ( (0 == (record->msg_options & GNUNET_DHT_RO_FIND_APPROXIMATE)) &&
        (0 != GNUNET_memcmp (&frc->bd->key,
                             query_hash)) )
   {
@@ -1060,6 +1063,101 @@ GDS_CLIENTS_handle_reply (const struct 
GDS_DATACACHE_BlockData *bd,
 }
 
 
+/* **************** HELLO logic ***************** */
+
+/**
+ * Handler for HELLO GET message. Reply to client
+ * with a URL of our HELLO.
+ *
+ * @param cls the client we received this message from
+ * @param msg the actual message received
+ *
+ */
+static void
+handle_dht_local_hello_get (void *cls,
+                            const struct GNUNET_MessageHeader *msg)
+{
+  struct ClientHandle *ch = cls;
+  char *url = GNUNET_HELLO_builder_to_url (GDS_my_hello,
+                                           &GDS_my_private_key);
+  size_t slen = strlen (url) + 1;
+  struct GNUNET_MessageHeader *hdr;
+  struct GNUNET_MQ_Envelope *env;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Handling request from local client for my HELLO\n");
+  env = GNUNET_MQ_msg_extra (hdr,
+                             slen,
+                             GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_URL);
+  memcpy (&hdr[1],
+          url,
+          slen);
+  GNUNET_free (url);
+  GNUNET_MQ_send (ch->mq,
+                  env);
+  GNUNET_SERVICE_client_continue (ch->client);
+}
+
+
+/**
+ * Process a client HELLO message received from the service.
+ *
+ * @param cls the client we received this message from
+ * @param hdr HELLO URL message from the service.
+ * @return #GNUNET_OK if @a hdr is well-formed
+ */
+static enum GNUNET_GenericReturnValue
+check_dht_local_hello_offer (void *cls,
+                             const struct GNUNET_MessageHeader *hdr)
+{
+  uint16_t len = ntohs (hdr->size);
+  const char *buf = (const char *) &hdr[1];
+
+  (void) cls;
+  if ('\0' != buf[len - sizeof (*hdr) - 1])
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Handler for HELLO OFFER message.  Try to use the
+ * HELLO to connect to another peer.
+ *
+ * @param cls the client we received this message from
+ * @param msg the actual message received
+ */
+static void
+handle_dht_local_hello_offer (void *cls,
+                              const struct GNUNET_MessageHeader *msg)
+{
+  struct ClientHandle *ch = cls;
+  const char *url = (const char *) &msg[1];
+  struct GNUNET_HELLO_Builder *b;
+  struct GNUNET_PeerIdentity pid;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Local client provided HELLO URL %s\n",
+              url);
+  b = GNUNET_HELLO_builder_from_url (url);
+  if (NULL == b)
+  {
+    GNUNET_break (0);
+    GNUNET_SERVICE_client_drop (ch->client);
+    return;
+  }
+  GNUNET_SERVICE_client_continue (ch->client);
+  GNUNET_HELLO_builder_iterate (b,
+                                &pid,
+                                &GDS_try_connect,
+                                &pid);
+  GNUNET_HELLO_builder_free (b);
+}
+
+
 /* ************* logic for monitors ************** */
 
 
@@ -1330,8 +1428,8 @@ response_action (void *cls,
                  bd->put_path_length * sizeof(struct GNUNET_DHT_PathElement));
   GNUNET_memcpy (path,
                  resp_ctx->get_path,
-                 resp_ctx->get_path_length * sizeof(struct
-                                                    GNUNET_DHT_PathElement));
+                 resp_ctx->get_path_length
+                 * sizeof(struct GNUNET_DHT_PathElement));
   GNUNET_memcpy (&path[resp_ctx->get_path_length],
                  bd->data,
                  bd->data_size);
@@ -1504,6 +1602,14 @@ GDS_CLIENTS_stop (void)
                            GNUNET_MESSAGE_TYPE_DHT_CLIENT_GET_RESULTS_KNOWN, \
                            struct GNUNET_DHT_ClientGetResultSeenMessage, \
                            NULL), \
+    GNUNET_MQ_hd_fixed_size (dht_local_hello_get,              \
+                             GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_GET, \
+                             struct GNUNET_MessageHeader, \
+                             NULL), \
+    GNUNET_MQ_hd_var_size (dht_local_hello_offer, \
+                           GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_URL, \
+                           struct GNUNET_MessageHeader, \
+                           NULL), \
     GNUNET_MQ_handler_end ())
 
 
diff --git a/src/dht/gnunet-service-dht_datacache.c 
b/src/dht/gnunet-service-dht_datacache.c
index cb778717b..880c72cb2 100644
--- a/src/dht/gnunet-service-dht_datacache.c
+++ b/src/dht/gnunet-service-dht_datacache.c
@@ -68,7 +68,7 @@ GDS_DATACACHE_handle_put (const struct 
GDS_DATACACHE_BlockData *bd)
                             1,
                             GNUNET_NO);
   GNUNET_CRYPTO_hash_xor (&bd->key,
-                          &my_identity_hash,
+                          &GDS_my_identity_hash,
                           &xor);
   r = GNUNET_DATACACHE_put (datacache,
                             &bd->key,
@@ -231,7 +231,15 @@ GDS_DATACACHE_handle_get (const struct GNUNET_HashCode 
*key,
                           GDS_DATACACHE_GetCallback gc,
                           void *gc_cls)
 {
-  struct GetRequestContext ctx;
+  struct GetRequestContext ctx = {
+    .eval = GNUNET_BLOCK_EVALUATION_REQUEST_VALID,
+    .key = *key,
+    .xquery = xquery,
+    .xquery_size = xquery_size,
+    .bg = bg,
+    .gc = gc,
+    .gc_cls = gc_cls
+  };
   unsigned int r;
 
   if (NULL == datacache)
@@ -240,13 +248,6 @@ GDS_DATACACHE_handle_get (const struct GNUNET_HashCode 
*key,
                             "# GET requests given to datacache",
                             1,
                             GNUNET_NO);
-  ctx.eval = GNUNET_BLOCK_EVALUATION_REQUEST_VALID;
-  ctx.key = *key;
-  ctx.xquery = xquery;
-  ctx.xquery_size = xquery_size;
-  ctx.bg = bg;
-  ctx.gc = gc;
-  ctx.gc_cls = gc_cls;
   r = GNUNET_DATACACHE_get (datacache,
                             key,
                             type,
@@ -261,85 +262,44 @@ GDS_DATACACHE_handle_get (const struct GNUNET_HashCode 
*key,
 }
 
 
-/**
- * Closure for #datacache_get_successors_iterator().
- */
-struct SuccContext
-{
-  /**
-   * Function to call on the result
-   */
-  GDS_DATACACHE_GetCallback cb;
-
-  /**
-   * Closure for @e cb.
-   */
-  void *cb_cls;
-
-};
-
-
-/**
- * Iterator for local get request results,
- *
- * @param cls closure with the `struct GNUNET_HashCode *` with the trail ID
- * @param key the key this data is stored under
- * @param size the size of the data identified by key
- * @param data the actual data
- * @param type the type of the data
- * @param exp when does this value expire?
- * @param put_path_length number of peers in @a put_path
- * @param put_path path the reply took on put
- * @return #GNUNET_OK to continue iteration, anything else
- * to stop iteration.
- */
-static enum GNUNET_GenericReturnValue
-datacache_get_successors_iterator (void *cls,
-                                   const struct GNUNET_HashCode *key,
-                                   size_t size,
-                                   const char *data,
-                                   enum GNUNET_BLOCK_Type type,
-                                   struct GNUNET_TIME_Absolute exp,
-                                   unsigned int put_path_length,
-                                   const struct
-                                   GNUNET_DHT_PathElement *put_path)
-{
-  const struct SuccContext *sc = cls;
-  struct GDS_DATACACHE_BlockData bd = {
-    .key = *key,
-    .expiration_time = exp,
-    .put_path = put_path,
-    .data = data,
-    .data_size = size,
-    .put_path_length = put_path_length,
-    .type = type
-  };
-
-  /* NOTE: The datacache currently does not store the RO from
-     the original 'put', so we don't know the 'correct' option
-     at this point anymore.  Thus, we conservatively assume
-     that recording is desired (for now). */
-  sc->cb (sc->cb_cls,
-          &bd);
-  return GNUNET_OK;
-}
-
-
-void
+enum GNUNET_BLOCK_EvaluationResult
 GDS_DATACACHE_get_closest (const struct GNUNET_HashCode *key,
+                           enum GNUNET_BLOCK_Type type,
+                           const void *xquery,
+                           size_t xquery_size,
+                           struct GNUNET_BLOCK_Group *bg,
                            GDS_DATACACHE_GetCallback cb,
                            void *cb_cls)
 {
-  struct SuccContext sc = {
-    .cb = cb,
-    .cb_cls = cb_cls
+  struct GetRequestContext ctx = {
+    .eval = GNUNET_BLOCK_EVALUATION_REQUEST_VALID,
+    .key = *key,
+    .xquery = xquery,
+    .xquery_size = xquery_size,
+    .bg = bg,
+    .gc = cb,
+    .gc_cls = cb_cls
   };
+  unsigned int r;
 
-  (void) GNUNET_DATACACHE_get_closest (datacache,
-                                       key,
-                                       NUM_CLOSEST,
-                                       &datacache_get_successors_iterator,
-                                       &sc);
+  if (NULL == datacache)
+    return GNUNET_BLOCK_EVALUATION_REQUEST_VALID;
+  GNUNET_STATISTICS_update (GDS_stats,
+                            "# GET closest requests given to datacache",
+                            1,
+                            GNUNET_NO);
+  r = GNUNET_DATACACHE_get_closest (datacache,
+                                    key,
+                                    type,
+                                    NUM_CLOSEST,
+                                    &datacache_get_iterator,
+                                    &ctx);
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "DATACACHE approximate GET for key %s completed (%d). %u results 
found.\n",
+       GNUNET_h2s (key),
+       ctx.eval,
+       r);
+  return ctx.eval;
 }
 
 
diff --git a/src/dht/gnunet-service-dht_datacache.h 
b/src/dht/gnunet-service-dht_datacache.h
index 691a51e0e..69a18c605 100644
--- a/src/dht/gnunet-service-dht_datacache.h
+++ b/src/dht/gnunet-service-dht_datacache.h
@@ -122,11 +122,20 @@ GDS_DATACACHE_handle_get (const struct GNUNET_HashCode 
*key,
  * another peer.
  *
  * @param key the location at which the peer is looking for data that is close
+ * @param type requested data type
+ * @param xquery extended query
+ * @param xquery_size number of bytes in xquery
+ * @param bg block group to use for evaluation of replies
  * @param cb function to call with the result
  * @param cb_cls closure for @a cb
+ * @return evaluation result for the local replies
  */
-void
+enum GNUNET_BLOCK_EvaluationResult
 GDS_DATACACHE_get_closest (const struct GNUNET_HashCode *key,
+                           enum GNUNET_BLOCK_Type type,
+                           const void *xquery,
+                           size_t xquery_size,
+                           struct GNUNET_BLOCK_Group *bg,
                            GDS_DATACACHE_GetCallback cb,
                            void *cb_cls);
 
diff --git a/src/dht/gnunet-service-dht_hello.c 
b/src/dht/gnunet-service-dht_hello.c
deleted file mode 100644
index 949456575..000000000
--- a/src/dht/gnunet-service-dht_hello.c
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
-     This file is part of GNUnet.
-     Copyright (C) 2011 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
- */
-
-/**
- * @file dht/gnunet-service-dht_hello.c
- * @brief GNUnet DHT integration with peerinfo
- * @author Christian Grothoff
- *
- * TODO:
- * - consider adding mechanism to remove expired HELLOs
- */
-#include "platform.h"
-#include "gnunet-service-dht.h"
-#include "gnunet-service-dht_hello.h"
-#include "gnunet_peerinfo_service.h"
-
-
-/**
- * Handle for peerinfo notifications.
- */
-static struct GNUNET_PEERINFO_NotifyContext *pnc;
-
-/**
- * Hash map of peers to HELLOs.
- */
-static struct GNUNET_CONTAINER_MultiPeerMap *peer_to_hello;
-
-
-/**
- * Obtain a peer's HELLO if available
- *
- * @param peer peer to look for a HELLO from
- * @return HELLO for the given peer
- */
-const struct GNUNET_HELLO_Message *
-GDS_HELLO_get (const struct GNUNET_PeerIdentity *peer)
-{
-  if (NULL == peer_to_hello)
-    return NULL;
-  return GNUNET_CONTAINER_multipeermap_get (peer_to_hello,
-                                            peer);
-}
-
-
-/**
- * Function called for each HELLO known to PEERINFO.
- *
- * @param cls closure
- * @param peer id of the peer, NULL for last call
- * @param hello hello message for the peer (can be NULL)
- * @param err_msg error message (not used)
- *
- * FIXME this is called once per address. Merge instead of replacing?
- */
-static void
-process_hello (void *cls,
-               const struct GNUNET_PeerIdentity *peer,
-               const struct GNUNET_HELLO_Message *hello,
-               const char *err_msg)
-{
-  struct GNUNET_TIME_Absolute ex;
-  struct GNUNET_HELLO_Message *hm;
-
-  if (NULL == hello)
-    return;
-  ex = GNUNET_HELLO_get_last_expiration (hello);
-  if (0 == GNUNET_TIME_absolute_get_remaining (ex).rel_value_us)
-    return;
-  GNUNET_STATISTICS_update (GDS_stats,
-                            "# HELLOs obtained from peerinfo",
-                            1,
-                            GNUNET_NO);
-  hm = GNUNET_CONTAINER_multipeermap_get (peer_to_hello,
-                                          peer);
-  GNUNET_free (hm);
-  hm = GNUNET_malloc (GNUNET_HELLO_size (hello));
-  GNUNET_memcpy (hm,
-                 hello,
-                 GNUNET_HELLO_size (hello));
-  GNUNET_assert (GNUNET_SYSERR !=
-                 GNUNET_CONTAINER_multipeermap_put (peer_to_hello,
-                                                    peer,
-                                                    hm,
-                                                    
GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE));
-}
-
-
-/**
- * Initialize HELLO subsystem.
- */
-void
-GDS_HELLO_init ()
-{
-  pnc = GNUNET_PEERINFO_notify (GDS_cfg,
-                                GNUNET_NO,
-                                &process_hello,
-                                NULL);
-  peer_to_hello = GNUNET_CONTAINER_multipeermap_create (256,
-                                                        GNUNET_NO);
-}
-
-
-/**
- * Free memory occopied by the HELLO.
- */
-static enum GNUNET_GenericReturnValue
-free_hello (void *cls,
-            const struct GNUNET_PeerIdentity *key,
-            void *hello)
-{
-  GNUNET_free (hello);
-  return GNUNET_OK;
-}
-
-
-/**
- * Shutdown HELLO subsystem.
- */
-void
-GDS_HELLO_done ()
-{
-  if (NULL != pnc)
-  {
-    GNUNET_PEERINFO_notify_cancel (pnc);
-    pnc = NULL;
-  }
-  if (NULL != peer_to_hello)
-  {
-    GNUNET_CONTAINER_multipeermap_iterate (peer_to_hello,
-                                           &free_hello,
-                                           NULL);
-    GNUNET_CONTAINER_multipeermap_destroy (peer_to_hello);
-  }
-}
-
-
-/* end of gnunet-service-dht_hello.c */
diff --git a/src/dht/gnunet-service-dht_hello.h 
b/src/dht/gnunet-service-dht_hello.h
deleted file mode 100644
index f8b90862d..000000000
--- a/src/dht/gnunet-service-dht_hello.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-     This file is part of GNUnet.
-     Copyright (C) 2011 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
- */
-
-/**
- * @file dht/gnunet-service-dht_hello.h
- * @brief GNUnet DHT integration with peerinfo
- * @author Christian Grothoff
- */
-#ifndef GNUNET_SERVICE_DHT_HELLO_H
-#define GNUNET_SERVICE_DHT_HELLO_H
-
-#include "gnunet_util_lib.h"
-#include "gnunet_hello_lib.h"
-
-/**
- * Obtain a peer's HELLO if available
- *
- * @param peer peer to look for a HELLO from
- * @return HELLO for the given peer
- */
-const struct GNUNET_HELLO_Message *
-GDS_HELLO_get (const struct GNUNET_PeerIdentity *peer);
-
-
-/**
- * Initialize HELLO subsystem.
- */
-void
-GDS_HELLO_init (void);
-
-
-/**
- * Shutdown HELLO subsystem.
- */
-void
-GDS_HELLO_done (void);
-
-#endif
diff --git a/src/dht/gnunet-service-dht_neighbours.c 
b/src/dht/gnunet-service-dht_neighbours.c
index cf150ea0c..2e25b4d1e 100644
--- a/src/dht/gnunet-service-dht_neighbours.c
+++ b/src/dht/gnunet-service-dht_neighbours.c
@@ -28,13 +28,10 @@
 #include "gnunet_constants.h"
 #include "gnunet_protocols.h"
 #include "gnunet_signatures.h"
-#include "gnunet_ats_service.h"
-#include "gnunet_core_service.h"
 #include "gnunet_hello_lib.h"
+#include "gnunet_hello_uri_lib.h"
 #include "gnunet-service-dht.h"
-#include "gnunet-service-dht_hello.h"
 #include "gnunet-service-dht_neighbours.h"
-#include "gnunet-service-dht_nse.h"
 #include "gnunet-service-dht_routing.h"
 #include "dht.h"
 
@@ -74,6 +71,7 @@
 #define DHT_MINIMUM_FIND_PEER_INTERVAL GNUNET_TIME_relative_multiply ( \
     GNUNET_TIME_UNIT_MINUTES, 2)
 
+
 /**
  * How long to additionally wait on average per #bucket_size to send out the
  * FIND PEER requests if we did successfully connect (!) to a a new peer and
@@ -83,7 +81,7 @@
  * top).  Also the range in which we randomize, so the effective value
  * is half of the number given here.
  */
-#define DHT_AVG_FIND_PEER_INTERVAL GNUNET_TIME_relative_multiply ( \
+#define DHT_AVG_FIND_PEER_INTERVAL GNUNET_TIME_relative_multiply (    \
     GNUNET_TIME_UNIT_SECONDS, 6)
 
 /**
@@ -91,11 +89,6 @@
  */
 #define GET_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
 
-/**
- * Hello address expiration
- */
-extern struct GNUNET_TIME_Relative hello_expiration;
-
 
 GNUNET_NETWORK_STRUCT_BEGIN
 
@@ -263,81 +256,134 @@ GNUNET_NETWORK_STRUCT_END
 /**
  * Entry for a peer in a bucket.
  */
-struct PeerInfo
+struct PeerInfo;
+
+
+/**
+ * List of targets that we can use to reach this peer.
+ */
+struct Target
 {
   /**
-   * Next peer entry (DLL)
+   * Kept in a DLL.
    */
-  struct PeerInfo *next;
+  struct Target *next;
 
   /**
-   *  Prev peer entry (DLL)
+   * Kept in a DLL.
    */
-  struct PeerInfo *prev;
+  struct Target *prev;
 
   /**
    * Handle for sending messages to this peer.
    */
-  struct GNUNET_MQ_Handle *mq;
+  struct GNUNET_DHTU_Target *utarget;
 
   /**
-   * What is the identity of the peer?
+   * Underlay providing this target.
    */
-  const struct GNUNET_PeerIdentity *id;
+  struct GDS_Underlay *u;
 
   /**
-   * Hash of @e id.
+   * Peer this is a target for.
    */
-  struct GNUNET_HashCode phash;
+  struct PeerInfo *pi;
 
   /**
-   * Which bucket is this peer in?
+   * Handle used to 'hold' the connection to this peer.
    */
-  int peer_bucket;
+  struct GNUNET_DHTU_PreferenceHandle *ph;
+
+  /**
+   * Set to number of messages are waiting for the transmission to finish.
+   */
+  unsigned int load;
+
+  /**
+   * Set to @a true if the target was dropped, but we could not clean
+   * up yet because @e busy was also true.
+   */
+  bool dropped;
+
 };
 
 
 /**
- * Peers are grouped into buckets.
+ * Entry for a peer in a bucket.
  */
-struct PeerBucket
+struct PeerInfo
 {
   /**
-   * Head of DLL
+   * What is the identity of the peer?
    */
-  struct PeerInfo *head;
+  struct GNUNET_PeerIdentity id;
 
   /**
-   * Tail of DLL
+   * Hash of @e id.
    */
-  struct PeerInfo *tail;
+  struct GNUNET_HashCode phash;
 
   /**
-   * Number of peers in the bucket.
+   * When does our HELLO from this peer expire?
    */
-  unsigned int peers_size;
+  struct GNUNET_TIME_Absolute hello_expiration;
+
+  /**
+   * Next peer entry (DLL)
+   */
+  struct PeerInfo *next;
+
+  /**
+   *  Prev peer entry (DLL)
+   */
+  struct PeerInfo *prev;
+
+  /**
+   * Head of DLL of targets for this peer.
+   */
+  struct Target *t_head;
+
+  /**
+   * Tail of DLL of targets for this peer.
+   */
+  struct Target *t_tail;
+
+  /**
+   * Block with a HELLO of this peer.
+   */
+  void *hello;
+
+  /**
+   * Number of bytes in @e hello.
+   */
+  size_t hello_size;
+
+  /**
+   * Which bucket is this peer in?
+   */
+  int peer_bucket;
 };
 
 
 /**
- * Information about a peer that we would like to connect to.
+ * Peers are grouped into buckets.
  */
-struct ConnectInfo
+struct PeerBucket
 {
   /**
-   * Handle to active HELLO offer operation, or NULL.
+   * Head of DLL
    */
-  struct GNUNET_TRANSPORT_OfferHelloHandle *oh;
+  struct PeerInfo *head;
 
   /**
-   * Handle to active connectivity suggestion operation, or NULL.
+   * Tail of DLL
    */
-  struct GNUNET_ATS_ConnectivitySuggestHandle *sh;
+  struct PeerInfo *tail;
 
   /**
-   * How much would we like to connect to this peer?
+   * Number of peers in the bucket.
    */
-  uint32_t strength;
+  unsigned int peers_size;
 };
 
 
@@ -373,12 +419,6 @@ static struct PeerBucket k_buckets[MAX_BUCKETS];
  */
 static struct GNUNET_CONTAINER_MultiPeerMap *all_connected_peers;
 
-/**
- * Hash map of all peers we would like to be connected to.
- * Values are of type `struct ConnectInfo`.
- */
-static struct GNUNET_CONTAINER_MultiPeerMap *all_desired_peers;
-
 /**
  * Maximum size for each bucket.
  */
@@ -389,30 +429,83 @@ static unsigned int bucket_size = DEFAULT_BUCKET_SIZE;
  */
 static struct GNUNET_SCHEDULER_Task *find_peer_task;
 
-/**
- * Identity of this peer.
- */
-static struct GNUNET_PeerIdentity my_identity;
 
 /**
- * Hash of the identity of this peer.
+ * Function called whenever we finished sending to a target.
+ * Marks the transmission as finished (and the target as ready
+ * for the next message).
+ *
+ * @param cls a `struct Target *`
  */
-struct GNUNET_HashCode my_identity_hash;
+static void
+send_done_cb (void *cls)
+{
+  struct Target *t = cls;
+  struct PeerInfo *pi = t->pi; /* NULL if t->dropped! */
 
-/**
- * Handle to CORE.
- */
-static struct GNUNET_CORE_Handle *core_api;
+  GNUNET_assert (t->load > 0);
+  t->load--;
+  if (0 < t->load)
+    return;
+  if (t->dropped)
+  {
+    GNUNET_free (t);
+    return;
+  }
+  /* move target back to the front */
+  GNUNET_CONTAINER_DLL_remove (pi->t_head,
+                               pi->t_tail,
+                               t);
+  GNUNET_CONTAINER_DLL_insert (pi->t_head,
+                               pi->t_tail,
+                               t);
+}
 
-/**
- * Handle to ATS connectivity.
- */
-static struct GNUNET_ATS_ConnectivityHandle *ats_ch;
 
 /**
- * Our private key.
+ * Send @a msg to @a pi.
+ *
+ * @param pi where to send the message
+ * @param msg message to send
  */
-static struct GNUNET_CRYPTO_EddsaPrivateKey my_private_key;
+static void
+do_send (struct PeerInfo *pi,
+         const struct GNUNET_MessageHeader *msg)
+{
+  struct Target *t;
+
+  for (t = pi->t_head;
+       NULL != t;
+       t = t->next)
+    if (t->load < MAXIMUM_PENDING_PER_PEER)
+      break;
+  if (NULL == t)
+  {
+    /* all targets busy, drop message */
+    GNUNET_STATISTICS_update (GDS_stats,
+                              "# messages dropped (underlays busy)",
+                              1,
+                              GNUNET_NO);
+    return;
+  }
+  t->load++;
+  /* rotate busy targets to the end */
+  if (MAXIMUM_PENDING_PER_PEER == t->load)
+  {
+    GNUNET_CONTAINER_DLL_remove (pi->t_head,
+                                 pi->t_tail,
+                                 t);
+    GNUNET_CONTAINER_DLL_insert_tail (pi->t_head,
+                                      pi->t_tail,
+                                      t);
+  }
+  GDS_u_send (t->u,
+              t->utarget,
+              msg,
+              ntohs (msg->size),
+              &send_done_cb,
+              t);
+}
 
 
 /**
@@ -449,7 +542,7 @@ sign_path (const struct GNUNET_HashCode *key,
   GNUNET_CRYPTO_hash (data,
                       data_size,
                       &hs.h_data);
-  GNUNET_CRYPTO_eddsa_sign (&my_private_key,
+  GNUNET_CRYPTO_eddsa_sign (&GDS_my_private_key,
                             &hs,
                             sig);
 }
@@ -469,7 +562,7 @@ find_bucket (const struct GNUNET_HashCode *hc)
   unsigned int bits;
 
   GNUNET_CRYPTO_hash_xor (hc,
-                          &my_identity_hash,
+                          &GDS_my_identity_hash,
                           &xor);
   bits = GNUNET_CRYPTO_hash_count_leading_zeros (&xor);
   if (bits == MAX_BUCKETS)
@@ -482,171 +575,6 @@ find_bucket (const struct GNUNET_HashCode *hc)
 }
 
 
-/**
- * Function called when #GNUNET_TRANSPORT_offer_hello() is done.
- * Clean up the "oh" field in the @a cls
- *
- * @param cls a `struct ConnectInfo`
- */
-static void
-offer_hello_done (void *cls)
-{
-  struct ConnectInfo *ci = cls;
-
-  ci->oh = NULL;
-}
-
-
-/**
- * Function called for all entries in #all_desired_peers to clean up.
- *
- * @param cls NULL
- * @param peer peer the entry is for
- * @param value the value to remove
- * @return #GNUNET_YES
- */
-static enum GNUNET_GenericReturnValue
-free_connect_info (void *cls,
-                   const struct GNUNET_PeerIdentity *peer,
-                   void *value)
-{
-  struct ConnectInfo *ci = value;
-
-  (void) cls;
-  GNUNET_assert (GNUNET_YES ==
-                 GNUNET_CONTAINER_multipeermap_remove (all_desired_peers,
-                                                       peer,
-                                                       ci));
-  if (NULL != ci->sh)
-  {
-    GNUNET_ATS_connectivity_suggest_cancel (ci->sh);
-    ci->sh = NULL;
-  }
-  if (NULL != ci->oh)
-  {
-    GNUNET_TRANSPORT_offer_hello_cancel (ci->oh);
-    ci->oh = NULL;
-  }
-  GNUNET_free (ci);
-  return GNUNET_YES;
-}
-
-
-/**
- * Consider if we want to connect to a given peer, and if so
- * let ATS know.  If applicable, the HELLO is offered to the
- * TRANSPORT service.
- *
- * @param pid peer to consider connectivity requirements for
- * @param h a HELLO message, or NULL
- */
-static void
-try_connect (const struct GNUNET_PeerIdentity *pid,
-             const struct GNUNET_MessageHeader *h)
-{
-  int bucket_idx;
-  struct GNUNET_HashCode pid_hash;
-  struct ConnectInfo *ci;
-  uint32_t strength;
-  struct PeerBucket *bucket;
-
-  GNUNET_CRYPTO_hash (pid,
-                      sizeof(struct GNUNET_PeerIdentity),
-                      &pid_hash);
-  bucket_idx = find_bucket (&pid_hash);
-  if (bucket_idx < 0)
-  {
-    GNUNET_break (0);
-    return; /* self!? */
-  }
-  bucket = &k_buckets[bucket_idx];
-  ci = GNUNET_CONTAINER_multipeermap_get (all_desired_peers,
-                                          pid);
-  if (bucket->peers_size < bucket_size)
-    strength = (bucket_size - bucket->peers_size) * bucket_idx;
-  else
-    strength = 0;
-  if (GNUNET_YES ==
-      GNUNET_CONTAINER_multipeermap_contains (all_connected_peers,
-                                              pid))
-    strength *= 2; /* double for connected peers */
-  if ( (0 == strength) &&
-       (NULL != ci) )
-  {
-    /* release request */
-    GNUNET_assert (GNUNET_YES ==
-                   free_connect_info (NULL,
-                                      pid,
-                                      ci));
-    return;
-  }
-  if (NULL == ci)
-  {
-    ci = GNUNET_new (struct ConnectInfo);
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_CONTAINER_multipeermap_put (all_desired_peers,
-                                                      pid,
-                                                      ci,
-                                                      
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  }
-  if ( (NULL != ci->oh) &&
-       (NULL != h) )
-    GNUNET_TRANSPORT_offer_hello_cancel (ci->oh);
-  if (NULL != h)
-    ci->oh = GNUNET_TRANSPORT_offer_hello (GDS_cfg,
-                                           h,
-                                           &offer_hello_done,
-                                           ci);
-  if ( (NULL != ci->sh) &&
-       (ci->strength != strength) )
-    GNUNET_ATS_connectivity_suggest_cancel (ci->sh);
-  if (ci->strength != strength)
-  {
-    ci->sh = GNUNET_ATS_connectivity_suggest (ats_ch,
-                                              pid,
-                                              strength);
-    ci->strength = strength;
-  }
-}
-
-
-/**
- * Function called for each peer in #all_desired_peers during
- * #update_connect_preferences() if we have reason to adjust
- * the strength of our desire to keep connections to certain
- * peers.  Calls #try_connect() to update the calculations for
- * the given @a pid.
- *
- * @param cls NULL
- * @param pid peer to update
- * @param value unused
- * @return #GNUNET_YES (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-update_desire_strength (void *cls,
-                        const struct GNUNET_PeerIdentity *pid,
-                        void *value)
-{
-  (void) cls;
-  (void) value;
-  try_connect (pid,
-               NULL);
-  return GNUNET_YES;
-}
-
-
-/**
- * Update our preferences for connectivity as given to ATS.
- */
-static void
-update_connect_preferences (void)
-{
-  GNUNET_CONTAINER_multipeermap_iterate (all_desired_peers,
-                                         &update_desire_strength,
-                                         NULL);
-}
-
-
 /**
  * Add each of the peers we already know to the Bloom filter of
  * the request so that we don't get duplicate HELLOs.
@@ -702,7 +630,7 @@ send_find_peer_message (void *cls)
         GNUNET_CRYPTO_QUALITY_WEAK,
         GNUNET_TIME_relative_multiply (
           DHT_AVG_FIND_PEER_INTERVAL,
-          100 * (1 + newly_found_peers) / bucket_size).rel_value_us);
+          1 + 100 * (1 + newly_found_peers) / bucket_size).rel_value_us);
     newly_found_peers = 0;
     GNUNET_assert (NULL == find_peer_task);
     find_peer_task =
@@ -719,7 +647,7 @@ send_find_peer_message (void *cls)
     struct GNUNET_CONTAINER_BloomFilter *peer_bf;
 
     bg = GNUNET_BLOCK_group_create (GDS_block_context,
-                                    GNUNET_BLOCK_TYPE_DHT_HELLO,
+                                    GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
                                     GNUNET_CRYPTO_random_u32 (
                                       GNUNET_CRYPTO_QUALITY_WEAK,
                                       UINT32_MAX),
@@ -736,12 +664,12 @@ send_find_peer_message (void *cls)
                                            DHT_BLOOM_SIZE,
                                            GNUNET_CONSTANTS_BLOOMFILTER_K);
     if (GNUNET_OK !=
-        GDS_NEIGHBOURS_handle_get (GNUNET_BLOCK_TYPE_DHT_HELLO,
-                                   GNUNET_DHT_RO_FIND_PEER
+        GDS_NEIGHBOURS_handle_get (GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
+                                   GNUNET_DHT_RO_FIND_APPROXIMATE
                                    | GNUNET_DHT_RO_RECORD_ROUTE,
                                    FIND_PEER_REPLICATION_LEVEL,
                                    0, /* hop count */
-                                   &my_identity_hash,
+                                   &GDS_my_identity_hash,
                                    NULL, 0, /* xquery */
                                    bg,
                                    peer_bf))
@@ -765,123 +693,180 @@ send_find_peer_message (void *cls)
 
 
 /**
- * Method called whenever a peer connects.
+ * The list of the first #bucket_size peers of @a bucket
+ * changed. We should thus make sure we have called 'hold'
+ * all of the first bucket_size peers!
  *
- * @param cls closure
- * @param peer peer identity this notification is about
- * @param mq message queue for sending messages to @a peer
- * @return our `struct PeerInfo` for @a peer
+ * @param[in,out] bucket the bucket where the peer set changed
  */
-static void *
-handle_core_connect (void *cls,
-                     const struct GNUNET_PeerIdentity *peer,
-                     struct GNUNET_MQ_Handle *mq)
+static void
+update_hold (struct PeerBucket *bucket)
+{
+  unsigned int off = 0;
+
+  /* find the peer -- we just go over all of them, should
+     be hardly any more expensive than just finding the 'right'
+     one. */
+  for (struct PeerInfo *pos = bucket->head;
+       NULL != pos;
+       pos = pos->next)
+  {
+    if (off > bucket_size)
+      break;   /* We only hold up to #bucket_size peers per bucket */
+    off++;
+    for (struct Target *tp = pos->t_head;
+         NULL != tp;
+         tp = tp->next)
+      if (NULL == tp->ph)
+        tp->ph = GDS_u_hold (tp->u,
+                             tp->utarget);
+  }
+}
+
+
+void
+GDS_u_connect (void *cls,
+               struct GNUNET_DHTU_Target *target,
+               const struct GNUNET_PeerIdentity *pid,
+               void **ctx)
 {
+  struct GDS_Underlay *u = cls;
   struct PeerInfo *pi;
   struct PeerBucket *bucket;
+  bool do_hold = false;
 
-  (void) cls;
   /* Check for connect to self message */
-  if (0 == GNUNET_memcmp (&my_identity,
-                          peer))
-    return NULL;
+  if (0 == GNUNET_memcmp (&GDS_my_identity,
+                          pid))
+    return;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Connected to peer %s\n",
-              GNUNET_i2s (peer));
-  GNUNET_assert (NULL ==
-                 GNUNET_CONTAINER_multipeermap_get (all_connected_peers,
-                                                    peer));
-  GNUNET_STATISTICS_update (GDS_stats,
-                            "# peers connected",
-                            1,
-                            GNUNET_NO);
-  pi = GNUNET_new (struct PeerInfo);
-  pi->id = peer;
-  pi->mq = mq;
-  GNUNET_CRYPTO_hash (peer,
-                      sizeof(struct GNUNET_PeerIdentity),
-                      &pi->phash);
-  pi->peer_bucket = find_bucket (&pi->phash);
-  GNUNET_assert ( (pi->peer_bucket >= 0) &&
-                  ((unsigned int) pi->peer_bucket < MAX_BUCKETS));
-  bucket = &k_buckets[pi->peer_bucket];
-  GNUNET_CONTAINER_DLL_insert_tail (bucket->head,
-                                    bucket->tail,
-                                    pi);
-  bucket->peers_size++;
-  closest_bucket = GNUNET_MAX (closest_bucket,
-                               (unsigned int) pi->peer_bucket + 1);
-  GNUNET_assert (GNUNET_OK ==
-                 GNUNET_CONTAINER_multipeermap_put (all_connected_peers,
-                                                    pi->id,
-                                                    pi,
-                                                    
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  if (bucket->peers_size <= bucket_size)
+              GNUNET_i2s (pid));
+  pi = GNUNET_CONTAINER_multipeermap_get (all_connected_peers,
+                                          pid);
+  if (NULL == pi)
   {
-    update_connect_preferences ();
-    newly_found_peers++;
+    GNUNET_STATISTICS_update (GDS_stats,
+                              "# peers connected",
+                              1,
+                              GNUNET_NO);
+    pi = GNUNET_new (struct PeerInfo);
+    pi->id = *pid;
+    GNUNET_CRYPTO_hash (pid,
+                        sizeof(*pid),
+                        &pi->phash);
+    pi->peer_bucket = find_bucket (&pi->phash);
+    GNUNET_assert ( (pi->peer_bucket >= 0) &&
+                    ((unsigned int) pi->peer_bucket < MAX_BUCKETS));
+    bucket = &k_buckets[pi->peer_bucket];
+    GNUNET_CONTAINER_DLL_insert_tail (bucket->head,
+                                      bucket->tail,
+                                      pi);
+    bucket->peers_size++;
+    closest_bucket = GNUNET_MAX (closest_bucket,
+                                 (unsigned int) pi->peer_bucket + 1);
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_CONTAINER_multipeermap_put (all_connected_peers,
+                                                      &pi->id,
+                                                      pi,
+                                                      
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+    if (bucket->peers_size <= bucket_size)
+    {
+      newly_found_peers++;
+      do_hold = true;
+    }
+    if ( (1 == GNUNET_CONTAINER_multipeermap_size (all_connected_peers)) &&
+         (GNUNET_YES != disable_try_connect) )
+    {
+      /* got a first connection, good time to start with FIND PEER requests... 
*/
+      GNUNET_assert (NULL == find_peer_task);
+      find_peer_task = GNUNET_SCHEDULER_add_now (&send_find_peer_message,
+                                                 NULL);
+    }
   }
-  if ( (1 == GNUNET_CONTAINER_multipeermap_size (all_connected_peers)) &&
-       (GNUNET_YES != disable_try_connect) )
   {
-    /* got a first connection, good time to start with FIND PEER requests... */
-    GNUNET_assert (NULL == find_peer_task);
-    find_peer_task = GNUNET_SCHEDULER_add_now (&send_find_peer_message,
-                                               NULL);
+    struct Target *t;
+
+    t = GNUNET_new (struct Target);
+    t->u = u;
+    t->utarget = target;
+    t->pi = pi;
+    GNUNET_CONTAINER_DLL_insert (pi->t_head,
+                                 pi->t_tail,
+                                 t);
+    *ctx = t;
+
   }
-  return pi;
+  if (do_hold)
+    update_hold (bucket);
 }
 
 
-/**
- * Method called whenever a peer disconnects.
- *
- * @param cls closure
- * @param peer peer identity this notification is about
- * @param internal_cls our `struct PeerInfo` for @a peer
- */
-static void
-handle_core_disconnect (void *cls,
-                        const struct GNUNET_PeerIdentity *peer,
-                        void *internal_cls)
+void
+GDS_u_disconnect (void *ctx)
 {
-  struct PeerInfo *to_remove = internal_cls;
+  struct Target *t = ctx;
+  struct PeerInfo *pi;
   struct PeerBucket *bucket;
+  bool was_held = false;
 
-  (void) cls;
   /* Check for disconnect from self message (on shutdown) */
-  if (NULL == to_remove)
+  if (NULL == t)
     return;
+  pi = t->pi;
+  GNUNET_CONTAINER_DLL_remove (pi->t_head,
+                               pi->t_tail,
+                               t);
+  if (NULL != t->ph)
+  {
+    GDS_u_drop (t->u,
+                t->ph);
+    t->ph = NULL;
+    was_held = true;
+  }
+  if (t->load > 0)
+  {
+    t->dropped = true;
+    t->pi = NULL;
+  }
+  else
+  {
+    GNUNET_free (t);
+  }
+  if (NULL != pi->t_head)
+    return; /* got other connections still */
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Disconnected from peer %s\n",
-              GNUNET_i2s (peer));
+              GNUNET_i2s (&pi->id));
   GNUNET_STATISTICS_update (GDS_stats,
                             "# peers connected",
                             -1,
                             GNUNET_NO);
   GNUNET_assert (GNUNET_YES ==
                  GNUNET_CONTAINER_multipeermap_remove (all_connected_peers,
-                                                       peer,
-                                                       to_remove));
+                                                       &pi->id,
+                                                       pi));
   if ( (0 == GNUNET_CONTAINER_multipeermap_size (all_connected_peers)) &&
        (GNUNET_YES != disable_try_connect))
   {
     GNUNET_SCHEDULER_cancel (find_peer_task);
     find_peer_task = NULL;
   }
-  GNUNET_assert (to_remove->peer_bucket >= 0);
-  bucket = &k_buckets[to_remove->peer_bucket];
+  GNUNET_assert (pi->peer_bucket >= 0);
+  bucket = &k_buckets[pi->peer_bucket];
   GNUNET_CONTAINER_DLL_remove (bucket->head,
                                bucket->tail,
-                               to_remove);
+                               pi);
   GNUNET_assert (bucket->peers_size > 0);
   bucket->peers_size--;
+  if ( (was_held) &&
+       (bucket->peers_size >= bucket_size - 1) )
+    update_hold (bucket);
   while ( (closest_bucket > 0) &&
           (0 == k_buckets[closest_bucket - 1].peers_size))
     closest_bucket--;
-  if (bucket->peers_size < bucket_size)
-    update_connect_preferences ();
-  GNUNET_free (to_remove);
+  GNUNET_free (pi->hello);
+  GNUNET_free (pi);
 }
 
 
@@ -894,8 +879,8 @@ handle_core_disconnect (void *cls,
  * @return Some number of peers to forward the message to
  */
 static unsigned int
-get_forward_count (uint32_t hop_count,
-                   uint32_t target_replication)
+get_forward_count (uint16_t hop_count,
+                   uint16_t target_replication)
 {
   uint32_t random_value;
   uint32_t forward_count;
@@ -956,7 +941,7 @@ enum GNUNET_GenericReturnValue
 GDS_am_closest_peer (const struct GNUNET_HashCode *key,
                      const struct GNUNET_CONTAINER_BloomFilter *bloom)
 {
-  if (0 == GNUNET_memcmp (&my_identity_hash,
+  if (0 == GNUNET_memcmp (&GDS_my_identity_hash,
                           key))
     return GNUNET_YES;
   for (int bucket_num = find_bucket (key);
@@ -983,7 +968,7 @@ GDS_am_closest_peer (const struct GNUNET_HashCode *key,
          because an unfiltered peer exists, we are not the
          closest. */
       int delta = GNUNET_CRYPTO_hash_xorcmp (&pos->phash,
-                                             &my_identity_hash,
+                                             &GDS_my_identity_hash,
                                              key);
       switch (delta)
       {
@@ -1047,7 +1032,7 @@ select_peer (const struct GNUNET_HashCode *key,
       struct GNUNET_HashCode xor;
 
       GNUNET_CRYPTO_hash_xor (key,
-                              &my_identity_hash,
+                              &GDS_my_identity_hash,
                               &xor);
       best_bucket = GNUNET_CRYPTO_hash_count_leading_zeros (&xor);
     }
@@ -1067,13 +1052,14 @@ select_peer (const struct GNUNET_HashCode *key,
         if (count >= bucket_size)
           break; /* we only consider first #bucket_size entries per bucket */
         count++;
-        if (GNUNET_YES ==
-            GNUNET_CONTAINER_bloomfilter_test (bloom,
-                                               &pos->phash))
+        if ( (NULL != bloom) &&
+             (GNUNET_YES ==
+              GNUNET_CONTAINER_bloomfilter_test (bloom,
+                                                 &pos->phash)) )
         {
           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                       "Excluded peer `%s' due to BF match in greedy routing 
for %s\n",
-                      GNUNET_i2s (pos->id),
+                      GNUNET_i2s (&pos->id),
                       GNUNET_h2s (key));
           continue;
         }
@@ -1140,7 +1126,7 @@ select_peer (const struct GNUNET_HashCode *key,
     }
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Selected peer `%s' in greedy routing for %s\n",
-                GNUNET_i2s (chosen->id),
+                GNUNET_i2s (&chosen->id),
                 GNUNET_h2s (key));
     return chosen;
   } /* end of 'greedy' peer selection */
@@ -1155,22 +1141,24 @@ select_peer (const struct GNUNET_HashCode *key,
 
     for (unsigned int bc = 0; bc < closest_bucket; bc++)
     {
+      struct PeerBucket *bucket = &k_buckets[bc];
       unsigned int count = 0;
 
-      for (struct PeerInfo *pos = k_buckets[bc].head;
+      for (struct PeerInfo *pos = bucket->head;
            NULL != pos;
            pos = pos->next)
       {
         count++;
         if (count > bucket_size)
           break; /* limits search to #bucket_size peers per bucket */
-        if (GNUNET_YES ==
-            GNUNET_CONTAINER_bloomfilter_test (bloom,
-                                               &pos->phash))
+        if ( (NULL != bloom) &&
+             (GNUNET_YES ==
+              GNUNET_CONTAINER_bloomfilter_test (bloom,
+                                                 &pos->phash)) )
         {
           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                       "Excluded peer `%s' due to BF match in random routing 
for %s\n",
-                      GNUNET_i2s (pos->id),
+                      GNUNET_i2s (&pos->id),
                       GNUNET_h2s (key));
           continue;             /* Ignore filtered peers */
         }
@@ -1201,15 +1189,16 @@ select_peer (const struct GNUNET_HashCode *key,
         if (count > bucket_size)
           break; /* limits search to #bucket_size peers per bucket */
 
-        if (GNUNET_YES ==
-            GNUNET_CONTAINER_bloomfilter_test (bloom,
-                                               &pos->phash))
+        if ( (NULL != bloom) &&
+             (GNUNET_YES ==
+              GNUNET_CONTAINER_bloomfilter_test (bloom,
+                                                 &pos->phash)) )
           continue;             /* Ignore bloomfiltered peers */
         if (0 == selected--)
         {
           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                       "Selected peer `%s' in random routing for %s\n",
-                      GNUNET_i2s (pos->id),
+                      GNUNET_i2s (&pos->id),
                       GNUNET_h2s (key));
           return pos;
         }
@@ -1237,8 +1226,8 @@ select_peer (const struct GNUNET_HashCode *key,
 static unsigned int
 get_target_peers (const struct GNUNET_HashCode *key,
                   struct GNUNET_CONTAINER_BloomFilter *bloom,
-                  uint32_t hop_count,
-                  uint32_t target_replication,
+                  uint16_t hop_count,
+                  uint16_t target_replication,
                   struct PeerInfo ***targets)
 {
   unsigned int target;
@@ -1297,8 +1286,8 @@ get_target_peers (const struct GNUNET_HashCode *key,
 enum GNUNET_GenericReturnValue
 GDS_NEIGHBOURS_handle_put (const struct GDS_DATACACHE_BlockData *bd,
                            enum GNUNET_DHT_RouteOption options,
-                           uint32_t desired_replication_level,
-                           uint32_t hop_count,
+                           uint16_t desired_replication_level,
+                           uint16_t hop_count,
                            struct GNUNET_CONTAINER_BloomFilter *bf)
 {
   unsigned int target_count;
@@ -1317,18 +1306,20 @@ GDS_NEIGHBOURS_handle_put (const struct 
GDS_DATACACHE_BlockData *bd,
                               bd->put_path,
                               bd->put_path_length,
                               NULL, 0, /* get_path */
-                              &my_identity))
+                              &GDS_my_identity))
   {
     GNUNET_break_op (0);
     put_path_length = 0;
   }
 #endif
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Adding myself (%s) to PUT bloomfilter for %s\n",
-              GNUNET_i2s (&my_identity),
-              GNUNET_h2s (&bd->key));
+              "Adding myself (%s) to PUT bloomfilter for %s with RO(%s/%s)\n",
+              GNUNET_i2s (&GDS_my_identity),
+              GNUNET_h2s (&bd->key),
+              (options & GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE) ? "x" : "-",
+              (options & GNUNET_DHT_RO_RECORD_ROUTE) ? "R" : "-");
   GNUNET_CONTAINER_bloomfilter_add (bf,
-                                    &my_identity_hash);
+                                    &GDS_my_identity_hash);
   GNUNET_STATISTICS_update (GDS_stats,
                             "# PUT requests routed",
                             1,
@@ -1345,7 +1336,7 @@ GDS_NEIGHBOURS_handle_put (const struct 
GDS_DATACACHE_BlockData *bd,
                 "Routing PUT for %s terminates after %u hops at %s\n",
                 GNUNET_h2s (&bd->key),
                 (unsigned int) hop_count,
-                GNUNET_i2s (&my_identity));
+                GNUNET_i2s (&GDS_my_identity));
     return GNUNET_NO;
   }
   msize = bd->put_path_length * sizeof(struct GNUNET_DHT_PathElement)
@@ -1367,33 +1358,23 @@ GDS_NEIGHBOURS_handle_put (const struct 
GDS_DATACACHE_BlockData *bd,
   for (unsigned int i = 0; i < target_count; i++)
   {
     struct PeerInfo *target = targets[i];
-    struct GNUNET_MQ_Envelope *env;
     struct PeerPutMessage *ppm;
+    char buf[sizeof (*ppm) + msize] GNUNET_ALIGN;
     struct GNUNET_DHT_PathElement *pp;
 
-    if (GNUNET_MQ_get_length (target->mq) >= MAXIMUM_PENDING_PER_PEER)
-    {
-      /* skip */
-      GNUNET_STATISTICS_update (GDS_stats,
-                                "# P2P messages dropped due to full queue",
-                                1,
-                                GNUNET_NO);
-      skip_count++;
-      continue;
-    }
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Routing PUT for %s after %u hops to %s\n",
                 GNUNET_h2s (&bd->key),
                 (unsigned int) hop_count,
-                GNUNET_i2s (target->id));
-    env = GNUNET_MQ_msg_extra (ppm,
-                               msize,
-                               GNUNET_MESSAGE_TYPE_DHT_P2P_PUT);
-    ppm->options = htonl (options);
+                GNUNET_i2s (&target->id));
+    ppm = (struct PeerPutMessage *) buf;
+    ppm->header.type = htons (GNUNET_MESSAGE_TYPE_DHT_P2P_PUT);
+    ppm->header.size = htons (sizeof (buf));
     ppm->type = htonl (bd->type);
-    ppm->hop_count = htonl (hop_count + 1);
-    ppm->desired_replication_level = htonl (desired_replication_level);
-    ppm->put_path_length = htonl (put_path_length);
+    ppm->options = htons (options);
+    ppm->hop_count = htons (hop_count + 1);
+    ppm->desired_replication_level = htons (desired_replication_level);
+    ppm->put_path_length = htons (put_path_length);
     ppm->expiration_time = GNUNET_TIME_absolute_hton (bd->expiration_time);
     GNUNET_break (GNUNET_YES ==
                   GNUNET_CONTAINER_bloomfilter_test (bf,
@@ -1417,15 +1398,15 @@ GDS_NEIGHBOURS_handle_put (const struct 
GDS_DATACACHE_BlockData *bd,
                  bd->data_size,
                  bd->expiration_time,
                  &pp[put_path_length - 1].pred,
-                 target->id,
+                 &target->id,
                  &pp[put_path_length - 1].sig);
     }
 
     GNUNET_memcpy (&pp[put_path_length],
                    bd->data,
                    bd->data_size);
-    GNUNET_MQ_send (target->mq,
-                    env);
+    do_send (target,
+             &ppm->header);
   }
   GNUNET_free (targets);
   GNUNET_STATISTICS_update (GDS_stats,
@@ -1439,8 +1420,8 @@ GDS_NEIGHBOURS_handle_put (const struct 
GDS_DATACACHE_BlockData *bd,
 enum GNUNET_GenericReturnValue
 GDS_NEIGHBOURS_handle_get (enum GNUNET_BLOCK_Type type,
                            enum GNUNET_DHT_RouteOption options,
-                           uint32_t desired_replication_level,
-                           uint32_t hop_count,
+                           uint16_t desired_replication_level,
+                           uint16_t hop_count,
                            const struct GNUNET_HashCode *key,
                            const void *xquery,
                            size_t xquery_size,
@@ -1466,18 +1447,21 @@ GDS_NEIGHBOURS_handle_get (enum GNUNET_BLOCK_Type type,
                                    desired_replication_level,
                                    &targets);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Adding myself (%s) to GET bloomfilter for %s\n",
-              GNUNET_i2s (&my_identity),
-              GNUNET_h2s (key));
+              "Adding myself (%s) to GET bloomfilter for %s with RO(%s/%s)\n",
+              GNUNET_i2s (&GDS_my_identity),
+              GNUNET_h2s (key),
+              (options & GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE) ? "x" : "-",
+              (options & GNUNET_DHT_RO_RECORD_ROUTE) ? "R" : "-");
+
   GNUNET_CONTAINER_bloomfilter_add (peer_bf,
-                                    &my_identity_hash);
+                                    &GDS_my_identity_hash);
   if (0 == target_count)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Routing GET for %s terminates after %u hops at %s\n",
                 GNUNET_h2s (key),
                 (unsigned int) hop_count,
-                GNUNET_i2s (&my_identity));
+                GNUNET_i2s (&GDS_my_identity));
     return GNUNET_NO;
   }
   if (GNUNET_OK !=
@@ -1504,32 +1488,22 @@ GDS_NEIGHBOURS_handle_get (enum GNUNET_BLOCK_Type type,
   for (unsigned int i = 0; i < target_count; i++)
   {
     struct PeerInfo *target = targets[i];
-    struct GNUNET_MQ_Envelope *env;
     struct PeerGetMessage *pgm;
+    char buf[sizeof (*pgm) + msize] GNUNET_ALIGN;
     char *xq;
 
-    if (GNUNET_MQ_get_length (target->mq) >= MAXIMUM_PENDING_PER_PEER)
-    {
-      /* skip */
-      GNUNET_STATISTICS_update (GDS_stats,
-                                "# P2P messages dropped due to full queue",
-                                1,
-                                GNUNET_NO);
-      skip_count++;
-      continue;
-    }
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Routing GET for %s after %u hops to %s\n",
                 GNUNET_h2s (key),
                 (unsigned int) hop_count,
-                GNUNET_i2s (target->id));
-    env = GNUNET_MQ_msg_extra (pgm,
-                               msize,
-                               GNUNET_MESSAGE_TYPE_DHT_P2P_GET);
-    pgm->options = htonl (options);
+                GNUNET_i2s (&target->id));
+    pgm = (struct PeerGetMessage *) buf;
+    pgm->header.type = htons (GNUNET_MESSAGE_TYPE_DHT_P2P_GET);
+    pgm->header.size = htons (sizeof (buf));
     pgm->type = htonl (type);
-    pgm->hop_count = htonl (hop_count + 1);
-    pgm->desired_replication_level = htonl (desired_replication_level);
+    pgm->options = htons (options);
+    pgm->hop_count = htons (hop_count + 1);
+    pgm->desired_replication_level = htons (desired_replication_level);
     pgm->xquery_size = htonl (xquery_size);
     pgm->bf_mutator = bf_nonce;
     GNUNET_break (GNUNET_YES ==
@@ -1547,8 +1521,8 @@ GDS_NEIGHBOURS_handle_get (enum GNUNET_BLOCK_Type type,
     GNUNET_memcpy (&xq[xquery_size],
                    reply_bf,
                    reply_bf_size);
-    GNUNET_MQ_send (target->mq,
-                    env);
+    do_send (target,
+             &pgm->header);
   }
   GNUNET_STATISTICS_update (GDS_stats,
                             "# GET messages queued for transmission",
@@ -1575,8 +1549,6 @@ GDS_NEIGHBOURS_handle_reply (struct PeerInfo *pi,
                              unsigned int get_path_length,
                              const struct GNUNET_DHT_PathElement *get_path)
 {
-  struct GNUNET_MQ_Envelope *env;
-  struct PeerResultMessage *prm;
   struct GNUNET_DHT_PathElement *paths;
   size_t msize;
   unsigned int ppl = bd->put_path_length;
@@ -1591,7 +1563,7 @@ GDS_NEIGHBOURS_handle_reply (struct PeerInfo *pi,
                               bd->put_path_length,
                               get_path,
                               get_path_length,
-                              &my_identity))
+                              &GDS_my_identity))
   {
     GNUNET_break_op (0);
     get_path_length = 0;
@@ -1622,82 +1594,67 @@ GDS_NEIGHBOURS_handle_reply (struct PeerInfo *pi,
     GNUNET_break (0);
     return;
   }
-  if (GNUNET_MQ_get_length (pi->mq) >= MAXIMUM_PENDING_PER_PEER)
-  {
-    /* skip */
-    GNUNET_STATISTICS_update (GDS_stats,
-                              "# P2P messages dropped due to full queue",
-                              1,
-                              GNUNET_NO);
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Peer queue full, ignoring reply for key %s\n",
-                GNUNET_h2s (&bd->key));
-    return;
-  }
-
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Forwarding reply for key %s to peer %s\n",
               GNUNET_h2s (query_hash),
-              GNUNET_i2s (pi->id));
+              GNUNET_i2s (&pi->id));
   GNUNET_STATISTICS_update (GDS_stats,
                             "# RESULT messages queued for transmission",
                             1,
                             GNUNET_NO);
-  env = GNUNET_MQ_msg_extra (prm,
-                             msize,
-                             GNUNET_MESSAGE_TYPE_DHT_P2P_RESULT);
-  prm->type = htonl (bd->type);
-  prm->put_path_length = htonl (ppl);
-  prm->get_path_length = htonl (get_path_length);
-  prm->expiration_time = GNUNET_TIME_absolute_hton (bd->expiration_time);
-  prm->key = *query_hash;
-  paths = (struct GNUNET_DHT_PathElement *) &prm[1];
-  GNUNET_memcpy (paths,
-                 bd->put_path,
-                 ppl * sizeof(struct GNUNET_DHT_PathElement));
-  GNUNET_memcpy (&paths[ppl],
-                 get_path,
-                 get_path_length * sizeof(struct GNUNET_DHT_PathElement));
-  /* 0 == get_path_length means path is not being tracked */
-  if (0 != get_path_length)
   {
-    /* Note that the signature in 'get_path' was not initialized before,
-       so this is crucial to avoid sending garbage. */
-    sign_path (&bd->key,
-               bd->data,
-               bd->data_size,
-               bd->expiration_time,
-               &paths[ppl + get_path_length - 1].pred,
-               pi->id,
-               &paths[ppl + get_path_length - 1].sig);
-  }
-  GNUNET_memcpy (&paths[ppl + get_path_length],
+    struct PeerResultMessage *prm;
+    char buf[sizeof (*prm) + msize] GNUNET_ALIGN;
+
+    prm = (struct PeerResultMessage *) buf;
+    prm->header.type = htons (GNUNET_MESSAGE_TYPE_DHT_P2P_RESULT);
+    prm->header.size = htons (sizeof (buf));
+    prm->type = htonl (bd->type);
+    prm->reserved = htonl (0);
+    prm->put_path_length = htons (ppl);
+    prm->get_path_length = htons (get_path_length);
+    prm->expiration_time = GNUNET_TIME_absolute_hton (bd->expiration_time);
+    prm->key = *query_hash;
+    paths = (struct GNUNET_DHT_PathElement *) &prm[1];
+    if (NULL != bd->put_path)
+    {
+      GNUNET_memcpy (paths,
+                     bd->put_path,
+                     ppl * sizeof(struct GNUNET_DHT_PathElement));
+    }
+    else
+    {
+      GNUNET_assert (0 == ppl);
+    }
+    if (NULL != get_path)
+    {
+      GNUNET_memcpy (&paths[ppl],
+                     get_path,
+                     get_path_length * sizeof(struct GNUNET_DHT_PathElement));
+    }
+    else
+    {
+      GNUNET_assert (0 == get_path_length);
+    }
+    /* 0 == get_path_length means path is not being tracked */
+    if (0 != get_path_length)
+    {
+      /* Note that the signature in 'get_path' was not initialized before,
+         so this is crucial to avoid sending garbage. */
+      sign_path (&bd->key,
                  bd->data,
-                 bd->data_size);
-  GNUNET_MQ_send (pi->mq,
-                  env);
-}
-
-
-/**
- * To be called on core init.
- *
- * @param cls service closure
- * @param identity the public identity of this peer
- */
-static void
-core_init (void *cls,
-           const struct GNUNET_PeerIdentity *identity)
-{
-  (void) cls;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "CORE called, I am %s\n",
-              GNUNET_i2s (identity));
-  my_identity = *identity;
-  GNUNET_CRYPTO_hash (identity,
-                      sizeof(struct GNUNET_PeerIdentity),
-                      &my_identity_hash);
-  GNUNET_SERVICE_resume (GDS_service);
+                 bd->data_size,
+                 bd->expiration_time,
+                 &paths[ppl + get_path_length - 1].pred,
+                 &pi->id,
+                 &paths[ppl + get_path_length - 1].sig);
+    }
+    GNUNET_memcpy (&paths[ppl + get_path_length],
+                   bd->data,
+                   bd->data_size);
+    do_send (pi,
+             &prm->header);
+  }
 }
 
 
@@ -1713,7 +1670,7 @@ check_dht_p2p_put (void *cls,
                    const struct PeerPutMessage *put)
 {
   uint16_t msize = ntohs (put->header.size);
-  uint32_t putlen = ntohl (put->put_path_length);
+  uint16_t putlen = ntohs (put->put_path_length);
 
   (void) cls;
   if ( (msize <
@@ -1732,17 +1689,18 @@ check_dht_p2p_put (void *cls,
 /**
  * Core handler for p2p put requests.
  *
- * @param cls closure with the `struct PeerInfo` of the sender
+ * @param cls closure with the `struct Target` of the sender
  * @param message message
  */
 static void
 handle_dht_p2p_put (void *cls,
                     const struct PeerPutMessage *put)
 {
-  struct PeerInfo *peer = cls;
+  struct Target *t = cls;
+  struct PeerInfo *peer = t->pi;
   uint16_t msize = ntohs (put->header.size);
   enum GNUNET_DHT_RouteOption options
-    = (enum GNUNET_DHT_RouteOption) ntohl (put->options);
+    = (enum GNUNET_DHT_RouteOption) ntohs (put->options);
   struct GDS_DATACACHE_BlockData bd = {
     .key = put->key,
     .expiration_time = GNUNET_TIME_absolute_ntoh (put->expiration_time),
@@ -1750,16 +1708,18 @@ handle_dht_p2p_put (void *cls,
   };
   const struct GNUNET_DHT_PathElement *put_path
     = (const struct GNUNET_DHT_PathElement *) &put[1];
-  uint32_t putlen
-    = ntohl (put->put_path_length);
+  uint16_t putlen
+    = ntohs (put->put_path_length);
 
   bd.data_size = msize - (sizeof(*put)
                           + putlen * sizeof(struct GNUNET_DHT_PathElement));
   bd.data = &put_path[putlen];
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "PUT for `%s' from %s\n",
+              "PUT for `%s' from %s with RO (%s/%s)\n",
               GNUNET_h2s (&put->key),
-              GNUNET_i2s (peer->id));
+              GNUNET_i2s (&peer->id),
+              (options & GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE) ? "x" : "-",
+              (options & GNUNET_DHT_RO_RECORD_ROUTE) ? "R" : "-");
   if (GNUNET_TIME_absolute_is_past (bd.expiration_time))
   {
     GNUNET_STATISTICS_update (GDS_stats,
@@ -1830,6 +1790,14 @@ handle_dht_p2p_put (void *cls,
     bd.put_path_length = putlen + 1;
     if (0 != (options & GNUNET_DHT_RO_RECORD_ROUTE))
     {
+      GNUNET_memcpy (pp,
+                     put_path,
+                     putlen * sizeof(struct GNUNET_DHT_PathElement));
+      pp[putlen].pred = peer->id;
+      /* zero-out signature, not valid until we actually do forward! */
+      memset (&pp[putlen].sig,
+              0,
+              sizeof (pp[putlen].sig));
 #if SANITY_CHECKS
       for (unsigned int i = 0; i <= putlen; i++)
       {
@@ -1839,9 +1807,10 @@ handle_dht_p2p_put (void *cls,
                         GNUNET_memcmp (&pp[i].pred,
                                        &pp[j].pred));
         }
-        GNUNET_break (0 !=
-                      GNUNET_memcmp (&pp[i].pred,
-                                     peer->id));
+        if (i < putlen)
+          GNUNET_break (0 !=
+                        GNUNET_memcmp (&pp[i].pred,
+                                       &peer->id));
       }
       if (0 !=
           GNUNET_DHT_verify_path (&bd.key,
@@ -1851,21 +1820,12 @@ handle_dht_p2p_put (void *cls,
                                   bd.put_path,
                                   putlen,
                                   NULL, 0, /* get_path */
-                                  &my_identity))
+                                  &GDS_my_identity))
       {
         GNUNET_break_op (0);
         putlen = 0;
       }
 #endif
-      GNUNET_memcpy (pp,
-                     put_path,
-                     putlen * sizeof(struct GNUNET_DHT_PathElement));
-      pp[putlen].pred = *peer->id;
-      /* zero-out signature, not valid until we actually do forward! */
-      memset (&pp[putlen].sig,
-              0,
-              sizeof (pp[putlen].sig));
-      putlen++;
     }
     else
     {
@@ -1889,8 +1849,8 @@ handle_dht_p2p_put (void *cls,
       forwarded
         = GDS_NEIGHBOURS_handle_put (&bd,
                                      options,
-                                     ntohl (put->desired_replication_level),
-                                     ntohl (put->hop_count),
+                                     ntohs (put->desired_replication_level),
+                                     ntohs (put->hop_count),
                                      bf);
       /* notify monitoring clients */
       GDS_CLIENTS_process_put (options
@@ -1898,8 +1858,8 @@ handle_dht_p2p_put (void *cls,
                                 ? GNUNET_DHT_RO_LAST_HOP
                                 : 0),
                                &bd,
-                               ntohl (put->hop_count),
-                               ntohl (put->desired_replication_level));
+                               ntohs (put->hop_count),
+                               ntohs (put->desired_replication_level));
     }
     GNUNET_CONTAINER_bloomfilter_free (bf);
   }
@@ -1907,45 +1867,60 @@ handle_dht_p2p_put (void *cls,
 
 
 /**
- * We have received a FIND PEER request.  Send matching
- * HELLOs back.
+ * We have received a request for a HELLO.  Sends our
+ * HELLO back.
  *
- * @param pi sender of the FIND PEER request
+ * @param pi sender of the request
  * @param key peers close to this key are desired
  * @param bg group for filtering peers
  */
 static void
-handle_find_peer (struct PeerInfo *pi,
-                  const struct GNUNET_HashCode *query_hash,
-                  struct GNUNET_BLOCK_Group *bg)
+handle_find_my_hello (struct PeerInfo *pi,
+                      const struct GNUNET_HashCode *query_hash,
+                      struct GNUNET_BLOCK_Group *bg)
 {
-  int bucket_idx;
-  struct PeerBucket *bucket;
-  struct PeerInfo *peer;
-  unsigned int choice;
-  struct GDS_DATACACHE_BlockData bd = {
-    .type = GNUNET_BLOCK_TYPE_DHT_HELLO
-  };
-
-  /* first, check about our own HELLO */
-  if (NULL != GDS_my_hello)
+  size_t block_size = 0;
+
+  /* TODO: consider caching our HELLO block for a bit, to
+     avoid signing too often here... */
+  GNUNET_break (GNUNET_NO ==
+                GNUNET_HELLO_builder_to_block (GDS_my_hello,
+                                               &GDS_my_private_key,
+                                               NULL,
+                                               &block_size));
   {
-    bd.expiration_time = GNUNET_TIME_relative_to_absolute (
-      hello_expiration),
-    bd.key = my_identity_hash,
-    bd.data = GDS_my_hello;
-    bd.data_size = GNUNET_HELLO_size (
-      (const struct GNUNET_HELLO_Message *) GDS_my_hello);
-    GNUNET_break (bd.data_size >= sizeof(struct GNUNET_MessageHeader));
-    if (GNUNET_BLOCK_REPLY_OK_MORE ==
-        GNUNET_BLOCK_check_reply (GDS_block_context,
-                                  GNUNET_BLOCK_TYPE_DHT_HELLO,
-                                  bg,
-                                  &my_identity_hash,
-                                  NULL, 0,
-                                  bd.data,
-                                  bd.data_size))
+    char block[block_size];
+
+    if (GNUNET_OK !=
+        GNUNET_HELLO_builder_to_block (GDS_my_hello,
+                                       &GDS_my_private_key,
+                                       block,
+                                       &block_size))
     {
+      GNUNET_STATISTICS_update (GDS_stats,
+                                "# FIND PEER requests ignored due to lack of 
HELLO",
+                                1,
+                                GNUNET_NO);
+    }
+    else if (GNUNET_BLOCK_REPLY_OK_MORE ==
+             GNUNET_BLOCK_check_reply (GDS_block_context,
+                                       GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
+                                       bg,
+                                       &GDS_my_identity_hash,
+                                       NULL, 0,
+                                       block,
+                                       block_size))
+    {
+      struct GDS_DATACACHE_BlockData bd = {
+        .type = GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
+        .expiration_time
+          = GNUNET_TIME_relative_to_absolute (
+              GNUNET_HELLO_ADDRESS_EXPIRATION),
+        .key = GDS_my_identity_hash,
+        .data = block,
+        .data_size = block_size
+      };
+
       GDS_NEIGHBOURS_handle_reply (pi,
                                    &bd,
                                    query_hash,
@@ -1959,66 +1934,48 @@ handle_find_peer (struct PeerInfo *pi,
                                 GNUNET_NO);
     }
   }
-  else
-  {
-    GNUNET_STATISTICS_update (GDS_stats,
-                              "# FIND PEER requests ignored due to lack of 
HELLO",
-                              1,
-                              GNUNET_NO);
-  }
+}
 
-  /* then, also consider sending a random HELLO from the closest bucket */
-  /* FIXME: How can this be true? Shouldnt we just do find_bucket() ? */
-  if (0 ==
-      GNUNET_memcmp (&my_identity_hash,
-                     query_hash))
-    bucket_idx = closest_bucket - 1;
-  else
-    bucket_idx = GNUNET_MIN ((int) closest_bucket - 1,
-                             find_bucket (query_hash));
-  if (bucket_idx < 0)
-    return;
-  bucket = &k_buckets[bucket_idx];
-  if (bucket->peers_size == 0)
-    return;
-  choice = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
-                                     bucket->peers_size);
-  peer = bucket->head;
-  while (choice > 0)
-  {
-    GNUNET_assert (NULL != peer);
-    peer = peer->next;
-    choice--;
-  }
-  choice = bucket->peers_size;
 
+/**
+ * We have received a request for nearby HELLOs.  Sends matching
+ * HELLOs back.
+ *
+ * @param pi sender of the request
+ * @param key peers close to this key are desired
+ * @param bg group for filtering peers
+ */
+static void
+handle_find_local_hello (struct PeerInfo *pi,
+                         const struct GNUNET_HashCode *query_hash,
+                         struct GNUNET_BLOCK_Group *bg)
+{
+  /* Force non-random selection by hop count */
+  struct PeerInfo *peer;
+
+  peer = select_peer (query_hash,
+                      NULL,
+                      GDS_NSE_get () + 1);
+  if ( (NULL != peer->hello) &&
+       (! GNUNET_TIME_absolute_is_past (peer->hello_expiration)) &&
+       (GNUNET_BLOCK_REPLY_OK_MORE ==
+        GNUNET_BLOCK_check_reply (
+          GDS_block_context,
+          GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
+          bg,
+          &peer->phash,
+          NULL, 0,        /* xquery */
+          peer->hello,
+          peer->hello_size)) )
   {
-    const struct GNUNET_HELLO_Message *hello;
-    size_t hello_size;
+    struct GDS_DATACACHE_BlockData bd = {
+      .type = GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
+      .expiration_time = peer->hello_expiration,
+      .key = peer->phash,
+      .data = peer->hello,
+      .data_size = peer->hello_size
+    };
 
-    do
-    {
-      peer = peer->next;
-      if (0 == choice--)
-        return;                 /* no non-masked peer available */
-      if (NULL == peer)
-        peer = bucket->head;
-      hello = GDS_HELLO_get (peer->id);
-    } while ( (NULL == hello) ||
-              (GNUNET_BLOCK_REPLY_OK_MORE !=
-               GNUNET_BLOCK_check_reply (
-                 GDS_block_context,
-                 GNUNET_BLOCK_TYPE_DHT_HELLO,
-                 bg,
-                 &peer->phash,
-                 NULL, 0, /* xquery */
-                 hello,
-                 (hello_size = GNUNET_HELLO_size (hello)))));
-    bd.expiration_time = GNUNET_TIME_relative_to_absolute (
-      GNUNET_CONSTANTS_HELLO_ADDRESS_EXPIRATION);
-    bd.key = peer->phash;
-    bd.data = hello;
-    bd.data_size = hello_size;
     GDS_NEIGHBOURS_handle_reply (pi,
                                  &bd,
                                  query_hash,
@@ -2049,7 +2006,7 @@ handle_local_result (void *cls,
 /**
  * Check validity of p2p get request.
  *
- * @param cls closure with the `struct PeerInfo` of the sender
+ * @param cls closure with the `struct Target` of the sender
  * @param get the message
  * @return #GNUNET_OK if the message is well-formed
  */
@@ -2073,20 +2030,21 @@ check_dht_p2p_get (void *cls,
 /**
  * Core handler for p2p get requests.
  *
- * @param cls closure with the `struct PeerInfo` of the sender
+ * @param cls closure with the `struct Target` of the sender
  * @param get the message
  */
 static void
 handle_dht_p2p_get (void *cls,
                     const struct PeerGetMessage *get)
 {
-  struct PeerInfo *peer = cls;
+  struct Target *t = cls;
+  struct PeerInfo *peer = t->pi;
   uint16_t msize = ntohs (get->header.size);
   uint32_t xquery_size = ntohl (get->xquery_size);
-  uint32_t hop_count = ntohl (get->hop_count);
+  uint32_t hop_count = ntohs (get->hop_count);
   size_t reply_bf_size = msize - (sizeof(*get) + xquery_size);
   enum GNUNET_BLOCK_Type type = (enum GNUNET_BLOCK_Type) ntohl (get->type);
-  enum GNUNET_DHT_RouteOption options = (enum GNUNET_DHT_RouteOption)  ntohl (
+  enum GNUNET_DHT_RouteOption options = (enum GNUNET_DHT_RouteOption)  ntohs (
     get->options);
   enum GNUNET_BLOCK_ReplyEvaluationResult eval = GNUNET_BLOCK_REPLY_OK_MORE;
   const void *xquery = (const void *) &get[1];
@@ -2133,32 +2091,45 @@ handle_dht_p2p_get (void *cls,
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "GET for %s at %s after %u hops\n",
                 GNUNET_h2s (&get->key),
-                GNUNET_i2s (&my_identity),
+                GNUNET_i2s (&GDS_my_identity),
                 (unsigned int) hop_count);
-    /* local lookup (this may update the reply_bf) */
+    /* local lookup (this may update the bg) */
     if ( (0 != (options & GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE)) ||
          (GDS_am_closest_peer (&get->key,
                                peer_bf)) )
     {
-      if ((0 != (options & GNUNET_DHT_RO_FIND_PEER)))
+      if (GNUNET_BLOCK_TYPE_DHT_URL_HELLO == type)
       {
         GNUNET_STATISTICS_update (GDS_stats,
-                                  "# P2P FIND PEER requests processed",
+                                  "# P2P HELLO lookup requests processed",
                                   1,
                                   GNUNET_NO);
-        handle_find_peer (peer,
-                          &get->key,
-                          bg);
+        handle_find_my_hello (peer,
+                              &get->key,
+                              bg);
+        if (0 != (options & GNUNET_DHT_RO_FIND_APPROXIMATE))
+          handle_find_local_hello (peer,
+                                   &get->key,
+                                   bg);
       }
       else
       {
-        eval = GDS_DATACACHE_handle_get (&get->key,
-                                         type,
-                                         xquery,
-                                         xquery_size,
-                                         bg,
-                                         &handle_local_result,
-                                         peer);
+        if (0 != (options & GNUNET_DHT_RO_FIND_APPROXIMATE))
+          eval = GDS_DATACACHE_get_closest (&get->key,
+                                            type,
+                                            xquery,
+                                            xquery_size,
+                                            bg,
+                                            &handle_local_result,
+                                            peer);
+        else
+          eval = GDS_DATACACHE_handle_get (&get->key,
+                                           type,
+                                           xquery,
+                                           xquery_size,
+                                           bg,
+                                           &handle_local_result,
+                                           peer);
       }
     }
     else
@@ -2169,8 +2140,10 @@ handle_dht_p2p_get (void *cls,
                                 GNUNET_NO);
     }
 
-    /* remember request for routing replies */
-    GDS_ROUTING_add (peer->id,
+    /* remember request for routing replies
+       TODO: why should we do this if GNUNET_BLOCK_REPLY_OK_LAST == eval?
+    */
+    GDS_ROUTING_add (&peer->id,
                      type,
                      bg,      /* bg now owned by routing, but valid at least 
until end of this function! */
                      options,
@@ -2181,7 +2154,7 @@ handle_dht_p2p_get (void *cls,
     /* P2P forwarding */
     {
       bool forwarded = false;
-      uint32_t desired_replication_level = ntohl (
+      uint16_t desired_replication_level = ntohs (
         get->desired_replication_level);
 
       if (eval != GNUNET_BLOCK_REPLY_OK_LAST)
@@ -2270,8 +2243,8 @@ static enum GNUNET_GenericReturnValue
 check_dht_p2p_result (void *cls,
                       const struct PeerResultMessage *prm)
 {
-  uint32_t get_path_length = ntohl (prm->get_path_length);
-  uint32_t put_path_length = ntohl (prm->put_path_length);
+  uint16_t get_path_length = ntohs (prm->get_path_length);
+  uint16_t put_path_length = ntohs (prm->put_path_length);
   uint16_t msize = ntohs (prm->header.size);
 
   (void) cls;
@@ -2291,6 +2264,63 @@ check_dht_p2p_result (void *cls,
 }
 
 
+/**
+ * Callback function used to extract URIs from a builder.
+ * Called when we should consider connecting to a peer.
+ *
+ * @param cls closure pointing to a `struct GNUNET_PeerIdentity *`
+ * @param uri one of the URIs
+ */
+void
+GDS_try_connect (void *cls,
+                 const char *uri)
+{
+  const struct GNUNET_PeerIdentity *pid = cls;
+  struct GNUNET_HashCode phash;
+  int peer_bucket;
+  struct PeerBucket *bucket;
+
+  if (0 == GNUNET_memcmp (&GDS_my_identity,
+                          pid))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Got a HELLO for my own PID, ignoring it\n");
+    return; /* that's us! */
+  }
+  GNUNET_CRYPTO_hash (pid,
+                      sizeof(*pid),
+                      &phash);
+  peer_bucket = find_bucket (&phash);
+  GNUNET_assert ( (peer_bucket >= 0) &&
+                  ((unsigned int) peer_bucket < MAX_BUCKETS));
+  bucket = &k_buckets[peer_bucket];
+  if (bucket->peers_size >= bucket_size)
+    return; /* do not care */
+  for (struct PeerInfo *pi = bucket->head;
+       NULL != pi;
+       pi = pi->next)
+    if (0 ==
+        GNUNET_memcmp (&pi->id,
+                       pid))
+    {
+      /* already connected */
+      /* TODO: maybe consider 'uri' anyway as an additional
+         alternative address??? */
+      return;
+    }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Discovered peer %s at %s suitable for bucket %d (%u/%u), trying 
to connect\n",
+              GNUNET_i2s (pid),
+              uri,
+              peer_bucket,
+              bucket->peers_size,
+              bucket_size);
+  /* new peer that we like! */
+  GDS_u_try_connect (pid,
+                     uri);
+}
+
+
 /**
  * Core handler for p2p result messages.
  *
@@ -2301,13 +2331,15 @@ static void
 handle_dht_p2p_result (void *cls,
                        const struct PeerResultMessage *prm)
 {
-  struct PeerInfo *peer = cls;
+  struct Target *t = cls;
+  struct PeerInfo *peer = t->pi;
   uint16_t msize = ntohs (prm->header.size);
-  uint32_t get_path_length = ntohl (prm->get_path_length);
+  uint16_t get_path_length = ntohs (prm->get_path_length);
   struct GDS_DATACACHE_BlockData bd = {
     .expiration_time  = GNUNET_TIME_absolute_ntoh (prm->expiration_time),
     .put_path = (const struct GNUNET_DHT_PathElement *) &prm[1],
-    .put_path_length = ntohl (prm->put_path_length),
+    .put_path_length = ntohs (prm->put_path_length),
+    .key = prm->key,
     .type = ntohl (prm->type)
   };
   const struct GNUNET_DHT_PathElement *get_path
@@ -2363,41 +2395,26 @@ handle_dht_p2p_result (void *cls,
   }
 
   /* if we got a HELLO, consider it for our own routing table */
-  if (GNUNET_BLOCK_TYPE_DHT_HELLO == bd.type)
+  if (GNUNET_BLOCK_TYPE_DHT_URL_HELLO == bd.type)
   {
-    const struct GNUNET_MessageHeader *h = bd.data;
     struct GNUNET_PeerIdentity pid;
-
-    /* Should be a HELLO, validate and consider using it! */
-    if (bd.data_size < sizeof(struct GNUNET_HELLO_Message))
-    {
-      GNUNET_break (0);
-      return;
-    }
-    if (bd.data_size != ntohs (h->size))
-    {
-      GNUNET_break (0);
-      return;
-    }
-    if (GNUNET_OK !=
-        GNUNET_HELLO_get_id ((const struct GNUNET_HELLO_Message *) h,
-                             &pid))
-    {
-      GNUNET_break_op (0);
-      return;
-    }
-    if ( (GNUNET_YES != disable_try_connect) &&
-         (0 != GNUNET_memcmp (&my_identity,
-                              &pid)) )
-      try_connect (&pid,
-                   h);
+    struct GNUNET_HELLO_Builder *b;
+
+    b = GNUNET_HELLO_builder_from_block (bd.data,
+                                         bd.data_size);
+    if (GNUNET_YES != disable_try_connect)
+      GNUNET_HELLO_builder_iterate (b,
+                                    &pid,
+                                    &GDS_try_connect,
+                                    &pid);
+    GNUNET_HELLO_builder_free (b);
   }
 
   /* First, check if 'peer' is already on the path, and if
      so, truncate it instead of expanding. */
   for (unsigned int i = 0; i <= get_path_length; i++)
     if (0 == GNUNET_memcmp (&get_path[i].pred,
-                            peer->id))
+                            &peer->id))
     {
       process_reply_with_path (&bd,
                                &prm->key,
@@ -2412,7 +2429,7 @@ handle_dht_p2p_result (void *cls,
     GNUNET_memcpy (xget_path,
                    get_path,
                    get_path_length * sizeof(struct GNUNET_DHT_PathElement));
-    xget_path[get_path_length].pred = *peer->id;
+    xget_path[get_path_length].pred = peer->id;
     memset (&xget_path[get_path_length].sig,
             0,
             sizeof (xget_path[get_path_length].sig));
@@ -2423,24 +2440,151 @@ handle_dht_p2p_result (void *cls,
 }
 
 
-enum GNUNET_GenericReturnValue
-GDS_NEIGHBOURS_init ()
+/**
+ * Check validity of a p2p hello message.
+ *
+ * @param cls closure
+ * @param hello message
+ * @return #GNUNET_YES if the message is well-formed
+ */
+static enum GNUNET_GenericReturnValue
+check_dht_p2p_hello (void *cls,
+                     const struct GNUNET_MessageHeader *hello)
 {
+  struct Target *t = cls;
+  struct PeerInfo *peer = t->pi;
+  enum GNUNET_GenericReturnValue ret;
+  size_t hellob_size;
+  void *hellob;
+  struct GNUNET_TIME_Absolute expiration;
+
+  ret = GNUNET_HELLO_dht_msg_to_block (hello,
+                                       &peer->id,
+                                       &hellob,
+                                       &hellob_size,
+                                       &expiration);
+  GNUNET_free (hellob);
+  return ret;
+}
+
+
+/**
+ * Core handler for p2p HELLO messages.
+ *
+ * @param cls closure
+ * @param message message
+ */
+static void
+handle_dht_p2p_hello (void *cls,
+                      const struct GNUNET_MessageHeader *hello)
+{
+  struct Target *t = cls;
+  struct PeerInfo *peer = t->pi;
+
+  GNUNET_free (peer->hello);
+  peer->hello_size = 0;
+  GNUNET_break (GNUNET_OK ==
+                GNUNET_HELLO_dht_msg_to_block (hello,
+                                               &peer->id,
+                                               &peer->hello,
+                                               &peer->hello_size,
+                                               &peer->hello_expiration));
+}
+
+
+void
+GDS_u_receive (void *cls,
+               void **tctx,
+               void **sctx,
+               const void *message,
+               size_t message_size)
+{
+  struct Target *t = *tctx;
   struct GNUNET_MQ_MessageHandler core_handlers[] = {
     GNUNET_MQ_hd_var_size (dht_p2p_get,
                            GNUNET_MESSAGE_TYPE_DHT_P2P_GET,
                            struct PeerGetMessage,
-                           NULL),
+                           t),
     GNUNET_MQ_hd_var_size (dht_p2p_put,
                            GNUNET_MESSAGE_TYPE_DHT_P2P_PUT,
                            struct PeerPutMessage,
-                           NULL),
+                           t),
     GNUNET_MQ_hd_var_size (dht_p2p_result,
                            GNUNET_MESSAGE_TYPE_DHT_P2P_RESULT,
                            struct PeerResultMessage,
-                           NULL),
+                           t),
+    GNUNET_MQ_hd_var_size (dht_p2p_hello,
+                           GNUNET_MESSAGE_TYPE_DHT_P2P_HELLO,
+                           struct GNUNET_MessageHeader,
+                           t),
     GNUNET_MQ_handler_end ()
   };
+  const struct GNUNET_MessageHeader *mh = message;
+
+  (void) cls; /* the 'struct GDS_Underlay' */
+  (void) sctx; /* our receiver address */
+  if (NULL == t)
+  {
+    /* Received message claiming to originate from myself?
+       Ignore! */
+    GNUNET_break_op (0);
+    return;
+  }
+  if (message_size < sizeof (*mh))
+  {
+    GNUNET_break_op (0);
+    return;
+  }
+  if (message_size != ntohs (mh->size))
+  {
+    GNUNET_break_op (0);
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Handling message of type %u from peer %s\n",
+              ntohs (mh->type),
+              GNUNET_i2s (&t->pi->id));
+  if (GNUNET_OK !=
+      GNUNET_MQ_handle_message (core_handlers,
+                                mh))
+  {
+    GNUNET_break_op (0);
+    return;
+  }
+}
+
+
+/**
+ * Send @a msg to all peers in our buckets.
+ *
+ * @param msg message to broadcast
+ */
+void
+GDS_NEIGHBOURS_broadcast (const struct GNUNET_MessageHeader *msg)
+{
+  for (unsigned int bc = 0; bc<closest_bucket; bc++)
+  {
+    struct PeerBucket *bucket = &k_buckets[bc];
+    unsigned int count = 0;
+
+    for (struct PeerInfo *pos = bucket->head;
+         NULL != pos;
+         pos = pos->next)
+    {
+      if (count >= bucket_size)
+        break;   /* we only consider first #bucket_size entries per bucket */
+      count++;
+      do_send (pos,
+               msg);
+    }
+  }
+}
+
+
+enum GNUNET_GenericReturnValue
+GDS_NEIGHBOURS_init ()
+{
+
   unsigned long long temp_config_num;
 
   disable_try_connect
@@ -2457,45 +2601,8 @@ GDS_NEIGHBOURS_init ()
     = GNUNET_CONFIGURATION_get_value_yesno (GDS_cfg,
                                             "DHT",
                                             "CACHE_RESULTS");
-  {
-    char *keyfile;
-
-    if (GNUNET_OK !=
-        GNUNET_CONFIGURATION_get_value_filename (GDS_cfg,
-                                                 "PEER",
-                                                 "PRIVATE_KEY",
-                                                 &keyfile))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Core service is lacking HOSTKEY configuration setting.  
Exiting.\n");
-      return GNUNET_SYSERR;
-    }
-    if (GNUNET_SYSERR ==
-        GNUNET_CRYPTO_eddsa_key_from_file (keyfile,
-                                           GNUNET_YES,
-                                           &my_private_key))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to setup peer's private key\n");
-      GNUNET_free (keyfile);
-      return GNUNET_SYSERR;
-    }
-    GNUNET_free (keyfile);
-  }
-
-  ats_ch = GNUNET_ATS_connectivity_init (GDS_cfg);
-  core_api = GNUNET_CORE_connect (GDS_cfg,
-                                  NULL,
-                                  &core_init,
-                                  &handle_core_connect,
-                                  &handle_core_disconnect,
-                                  core_handlers);
-  if (NULL == core_api)
-    return GNUNET_SYSERR;
   all_connected_peers = GNUNET_CONTAINER_multipeermap_create (256,
                                                               GNUNET_YES);
-  all_desired_peers = GNUNET_CONTAINER_multipeermap_create (256,
-                                                            GNUNET_NO);
   return GNUNET_OK;
 }
 
@@ -2503,21 +2610,12 @@ GDS_NEIGHBOURS_init ()
 void
 GDS_NEIGHBOURS_done ()
 {
-  if (NULL == core_api)
+  if (NULL == all_connected_peers)
     return;
-  GNUNET_CORE_disconnect (core_api);
-  core_api = NULL;
   GNUNET_assert (0 ==
                  GNUNET_CONTAINER_multipeermap_size (all_connected_peers));
   GNUNET_CONTAINER_multipeermap_destroy (all_connected_peers);
   all_connected_peers = NULL;
-  GNUNET_CONTAINER_multipeermap_iterate (all_desired_peers,
-                                         &free_connect_info,
-                                         NULL);
-  GNUNET_CONTAINER_multipeermap_destroy (all_desired_peers);
-  all_desired_peers = NULL;
-  GNUNET_ATS_connectivity_done (ats_ch);
-  ats_ch = NULL;
   GNUNET_assert (NULL == find_peer_task);
 }
 
@@ -2525,7 +2623,7 @@ GDS_NEIGHBOURS_done ()
 struct GNUNET_PeerIdentity *
 GDS_NEIGHBOURS_get_id ()
 {
-  return &my_identity;
+  return &GDS_my_identity;
 }
 
 
diff --git a/src/dht/gnunet-service-dht_neighbours.h 
b/src/dht/gnunet-service-dht_neighbours.h
index 35bbb125d..fdaf655bd 100644
--- a/src/dht/gnunet-service-dht_neighbours.h
+++ b/src/dht/gnunet-service-dht_neighbours.h
@@ -30,13 +30,9 @@
 #include "gnunet_util_lib.h"
 #include "gnunet_block_lib.h"
 #include "gnunet_dht_service.h"
+#include "gnunet_dhtu_plugin.h"
 #include "gnunet-service-dht_datacache.h"
 
-/**
- * Hash of the identity of this peer.
- */
-extern struct GNUNET_HashCode my_identity_hash;
-
 
 struct PeerInfo;
 
@@ -67,8 +63,8 @@ GDS_NEIGHBOURS_lookup_peer (const struct GNUNET_PeerIdentity 
*target);
 enum GNUNET_GenericReturnValue
 GDS_NEIGHBOURS_handle_put (const struct GDS_DATACACHE_BlockData *bd,
                            enum GNUNET_DHT_RouteOption options,
-                           uint32_t desired_replication_level,
-                           uint32_t hop_count,
+                           uint16_t desired_replication_level,
+                           uint16_t hop_count,
                            struct GNUNET_CONTAINER_BloomFilter *bf);
 
 
@@ -92,8 +88,8 @@ GDS_NEIGHBOURS_handle_put (const struct 
GDS_DATACACHE_BlockData *bd,
 enum GNUNET_GenericReturnValue
 GDS_NEIGHBOURS_handle_get (enum GNUNET_BLOCK_Type type,
                            enum GNUNET_DHT_RouteOption options,
-                           uint32_t desired_replication_level,
-                           uint32_t hop_count,
+                           uint16_t desired_replication_level,
+                           uint16_t hop_count,
                            const struct GNUNET_HashCode *key,
                            const void *xquery,
                            size_t xquery_size,
@@ -136,6 +132,73 @@ GDS_am_closest_peer (const struct GNUNET_HashCode *key,
                      const struct GNUNET_CONTAINER_BloomFilter *bloom);
 
 
+/**
+ * Callback function used to extract URIs from a builder.
+ * Called when we should consider connecting to a peer.
+ *
+ * @param cls closure pointing to a `struct GNUNET_PeerIdentity *`
+ * @param uri one of the URIs
+ */
+void
+GDS_try_connect (void *cls,
+                 const char *uri);
+
+
+/**
+ * Function to call when we connect to a peer and can henceforth transmit to
+ * that peer.
+ *
+ * @param cls the closure, must be a `struct GDS_Underlay`
+ * @param target handle to the target,
+ *    pointer will remain valid until @e disconnect_cb is called
+ * @para pid peer identity,
+ *    pointer will remain valid until @e disconnect_cb is called
+ * @param[out] ctx storage space for DHT to use in association with this target
+ */
+void
+GDS_u_connect (void *cls,
+               struct GNUNET_DHTU_Target *target,
+               const struct GNUNET_PeerIdentity *pid,
+               void **ctx);
+
+
+/**
+ * Function to call when we disconnected from a peer and can henceforth
+ * cannot transmit to that peer anymore.
+ *
+ * @param[in] ctx storage space used by the DHT in association with this target
+ */
+void
+GDS_u_disconnect (void *ctx);
+
+
+/**
+ * Function to call when we receive a message.
+ *
+ * @param cls the closure
+ * @param origin where the message originated from
+ * @param[in,out] tctx ctx of target address where we received the message from
+ * @param[in,out] sctx ctx of our own source address at which we received the 
message
+ * @param message the message we received @param message_size number of
+ * bytes in @a message
+ */
+void
+GDS_u_receive (void *cls,
+               void **tctx,
+               void **sctx,
+               const void *message,
+               size_t message_size);
+
+
+/**
+ * Send @a msg to all peers in our buckets.
+ *
+ * @param msg message to broadcast
+ */
+void
+GDS_NEIGHBOURS_broadcast (const struct GNUNET_MessageHeader *msg);
+
+
 /**
  * Initialize neighbours subsystem.
  *
diff --git a/src/dht/gnunet-service-dht_nse.c b/src/dht/gnunet-service-dht_nse.c
deleted file mode 100644
index 58f18816a..000000000
--- a/src/dht/gnunet-service-dht_nse.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
-     This file is part of GNUnet.
-     Copyright (C) 2009, 2010, 2011 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
- */
-
-/**
- * @file dht/gnunet-service-dht_nse.c
- * @brief GNUnet DHT integration with NSE
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "gnunet_nse_service.h"
-#include "gnunet-service-dht.h"
-#include "gnunet-service-dht_nse.h"
-
-/**
- * log of the current network size estimate, used as the point where
- * we switch between random and deterministic routing.  Default
- * value of 4.0 is used if NSE module is not available (i.e. not
- * configured).
- */
-static double log_of_network_size_estimate = 4.0;
-
-/**
- * Network size estimation handle.
- */
-static struct GNUNET_NSE_Handle *nse;
-
-
-/**
- * Callback that is called when network size estimate is updated.
- *
- * @param cls closure
- * @param timestamp time when the estimate was received from the server (or 
created by the server)
- * @param logestimate the log(Base 2) value of the current network size 
estimate
- * @param std_dev standard deviation for the estimate
- *
- */
-static void
-update_network_size_estimate (void *cls,
-                              struct GNUNET_TIME_Absolute timestamp,
-                              double logestimate,
-                              double std_dev)
-{
-  GNUNET_STATISTICS_update (GDS_stats,
-                            "# Network size estimates received",
-                            1, GNUNET_NO);
-  /* do not allow estimates < 0.5 */
-  log_of_network_size_estimate = GNUNET_MAX (0.5, logestimate);
-}
-
-
-/**
- * Return the log of the current network size estimate.
- *
- * @return log of NSE
- */
-double
-GDS_NSE_get ()
-{
-  return log_of_network_size_estimate;
-}
-
-
-/**
- * Initialize NSE subsystem.
- */
-void
-GDS_NSE_init ()
-{
-  unsigned long long hops;
-
-  if ( (GNUNET_YES ==
-        GNUNET_CONFIGURATION_have_value (GDS_cfg,
-                                         "dht",
-                                         "FORCE_NSE")) &&
-       (GNUNET_OK ==
-        GNUNET_CONFIGURATION_get_value_number (GDS_cfg,
-                                               "dht",
-                                               "FORCE_NSE",
-                                               &hops)) )
-  {
-    log_of_network_size_estimate = (double) hops;
-    return;
-  }
-  nse = GNUNET_NSE_connect (GDS_cfg,
-                            &update_network_size_estimate,
-                            NULL);
-}
-
-
-/**
- * Shutdown NSE subsystem.
- */
-void
-GDS_NSE_done ()
-{
-  if (NULL != nse)
-  {
-    GNUNET_NSE_disconnect (nse);
-    nse = NULL;
-  }
-}
-
-
-/* end of gnunet-service-dht_nse.c */
diff --git a/src/dht/gnunet-service-dht_nse.h b/src/dht/gnunet-service-dht_nse.h
deleted file mode 100644
index e99389e74..000000000
--- a/src/dht/gnunet-service-dht_nse.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-     This file is part of GNUnet.
-     Copyright (C) 2011 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
- */
-
-/**
- * @file dht/gnunet-service-dht_nse.h
- * @brief GNUnet DHT integration with NSE
- * @author Christian Grothoff
- */
-#ifndef GNUNET_SERVICE_DHT_NSE_H
-#define GNUNET_SERVICE_DHT_NSE_H
-
-
-/**
- * Return the log of the current network size estimate.
- *
- * @return log of NSE
- */
-double
-GDS_NSE_get (void);
-
-
-/**
- * Initialize NSE subsystem.
- */
-void
-GDS_NSE_init (void);
-
-
-/**
- * Shutdown NSE subsystem.
- */
-void
-GDS_NSE_done (void);
-
-#endif
diff --git a/src/dht/gnunet-service-dht_routing.c 
b/src/dht/gnunet-service-dht_routing.c
index e7b5c3571..ec3f5b46f 100644
--- a/src/dht/gnunet-service-dht_routing.c
+++ b/src/dht/gnunet-service-dht_routing.c
@@ -151,7 +151,7 @@ process (void *cls,
     bdx.put_path_length = 0;
     bdx.put_path = NULL;
   }
-  if ( (0 == (rr->options & GNUNET_DHT_RO_FIND_PEER)) &&
+  if ( (0 == (rr->options & GNUNET_DHT_RO_FIND_APPROXIMATE)) &&
        (0 != GNUNET_memcmp (query_hash,
                             &bdx.key)) )
   {
diff --git a/src/dht/plugin_block_dht.c b/src/dht/plugin_block_dht.c
index 7c6fb9ed6..0dbe21af9 100644
--- a/src/dht/plugin_block_dht.c
+++ b/src/dht/plugin_block_dht.c
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet
-     Copyright (C) 2010, 2017 GNUnet e.V.
+     Copyright (C) 2010, 2017, 2022 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
@@ -28,6 +28,7 @@
 #include "platform.h"
 #include "gnunet_constants.h"
 #include "gnunet_hello_lib.h"
+#include "gnunet_hello_uri_lib.h"
 #include "gnunet_block_plugin.h"
 #include "gnunet_block_group_lib.h"
 
@@ -116,45 +117,53 @@ block_plugin_dht_evaluate (void *cls,
                            const void *reply_block,
                            size_t reply_block_size)
 {
-  const struct GNUNET_HELLO_Message *hello;
-  struct GNUNET_PeerIdentity pid;
-  const struct GNUNET_MessageHeader *msg;
-  struct GNUNET_HashCode phash;
-
-  if (type != GNUNET_BLOCK_TYPE_DHT_HELLO)
-    return GNUNET_BLOCK_EVALUATION_TYPE_NOT_SUPPORTED;
-  if (0 != xquery_size)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_EVALUATION_REQUEST_INVALID;
-  }
-  if (NULL == reply_block)
-    return GNUNET_BLOCK_EVALUATION_REQUEST_VALID;
-  if (reply_block_size < sizeof(struct GNUNET_MessageHeader))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_EVALUATION_RESULT_INVALID;
-  }
-  msg = reply_block;
-  if (reply_block_size != ntohs (msg->size))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_EVALUATION_RESULT_INVALID;
-  }
-  hello = reply_block;
-  if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
+  switch (type)
   {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_EVALUATION_RESULT_INVALID;
+  case GNUNET_BLOCK_TYPE_DHT_HELLO:
+    {
+      const struct GNUNET_HELLO_Message *hello;
+      struct GNUNET_PeerIdentity pid;
+      const struct GNUNET_MessageHeader *msg;
+      struct GNUNET_HashCode phash;
+
+      if (0 != xquery_size)
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_EVALUATION_REQUEST_INVALID;
+      }
+      if (NULL == reply_block)
+        return GNUNET_BLOCK_EVALUATION_REQUEST_VALID;
+      if (reply_block_size < sizeof(struct GNUNET_MessageHeader))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_EVALUATION_RESULT_INVALID;
+      }
+      msg = reply_block;
+      if (reply_block_size != ntohs (msg->size))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_EVALUATION_RESULT_INVALID;
+      }
+      hello = reply_block;
+      if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_EVALUATION_RESULT_INVALID;
+      }
+      GNUNET_CRYPTO_hash (&pid,
+                          sizeof(pid),
+                          &phash);
+      if (GNUNET_YES ==
+          GNUNET_BLOCK_GROUP_bf_test_and_set (group,
+                                              &phash))
+        return GNUNET_BLOCK_EVALUATION_OK_DUPLICATE;
+      return GNUNET_BLOCK_EVALUATION_OK_MORE;
+    }
+  case GNUNET_BLOCK_TYPE_DHT_URL_HELLO:
+    GNUNET_break (0); // legacy API not implemented
+  default:
+    return GNUNET_BLOCK_EVALUATION_TYPE_NOT_SUPPORTED;
   }
-  GNUNET_CRYPTO_hash (&pid,
-                      sizeof(pid),
-                      &phash);
-  if (GNUNET_YES ==
-      GNUNET_BLOCK_GROUP_bf_test_and_set (group,
-                                          &phash))
-    return GNUNET_BLOCK_EVALUATION_OK_DUPLICATE;
-  return GNUNET_BLOCK_EVALUATION_OK_MORE;
 }
 
 
@@ -171,19 +180,30 @@ block_plugin_dht_evaluate (void *cls,
  */
 static enum GNUNET_GenericReturnValue
 block_plugin_dht_check_query (void *cls,
-                                    enum GNUNET_BLOCK_Type type,
-                                    const struct GNUNET_HashCode *query,
-                                    const void *xquery,
-                                    size_t xquery_size)
+                              enum GNUNET_BLOCK_Type type,
+                              const struct GNUNET_HashCode *query,
+                              const void *xquery,
+                              size_t xquery_size)
 {
-  if (type != GNUNET_BLOCK_TYPE_DHT_HELLO)
-    return GNUNET_SYSERR;
-  if (0 != xquery_size)
+  switch (type)
   {
-    GNUNET_break_op (0);
-    return GNUNET_NO;
+  case GNUNET_BLOCK_TYPE_DHT_HELLO:
+    if (0 != xquery_size)
+    {
+      GNUNET_break_op (0);
+      return GNUNET_NO;
+    }
+    return GNUNET_OK;
+  case GNUNET_BLOCK_TYPE_DHT_URL_HELLO:
+    if (0 != xquery_size)
+    {
+      GNUNET_break_op (0);
+      return GNUNET_NO;
+    }
+    return GNUNET_OK;
+  default:
+    return GNUNET_SYSERR;
   }
-  return GNUNET_OK;
 }
 
 
@@ -199,37 +219,72 @@ block_plugin_dht_check_query (void *cls,
  */
 static enum GNUNET_GenericReturnValue
 block_plugin_dht_check_block (void *cls,
-                                    enum GNUNET_BLOCK_Type type,
-                                    const struct GNUNET_HashCode *query,
-                                    const void *block,
-                                    size_t block_size)
+                              enum GNUNET_BLOCK_Type type,
+                              const struct GNUNET_HashCode *query,
+                              const void *block,
+                              size_t block_size)
 {
-  const struct GNUNET_HELLO_Message *hello;
-  struct GNUNET_PeerIdentity pid;
-  const struct GNUNET_MessageHeader *msg;
-
-  if (type != GNUNET_BLOCK_TYPE_DHT_HELLO)
-    return GNUNET_SYSERR;
-  if (block_size < sizeof(struct GNUNET_MessageHeader))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_NO;
-  }
-  msg = block;
-  if (block_size != ntohs (msg->size))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_NO;
-  }
-  hello = block;
-  if (GNUNET_OK !=
-      GNUNET_HELLO_get_id (hello,
-                           &pid))
+  switch (type)
   {
-    GNUNET_break_op (0);
-    return GNUNET_NO;
+  case GNUNET_BLOCK_TYPE_DHT_HELLO:
+    {
+      const struct GNUNET_HELLO_Message *hello;
+      struct GNUNET_PeerIdentity pid;
+      const struct GNUNET_MessageHeader *msg;
+
+      if (block_size < sizeof(struct GNUNET_MessageHeader))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_NO;
+      }
+      msg = block;
+      if (block_size != ntohs (msg->size))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_NO;
+      }
+      hello = block;
+      if (GNUNET_OK !=
+          GNUNET_HELLO_get_id (hello,
+                               &pid))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_NO;
+      }
+      return GNUNET_OK;
+    }
+  case GNUNET_BLOCK_TYPE_DHT_URL_HELLO:
+    {
+      struct GNUNET_HELLO_Builder *b;
+      struct GNUNET_PeerIdentity pid;
+      struct GNUNET_HashCode h_pid;
+
+      b = GNUNET_HELLO_builder_from_block (block,
+                                           block_size);
+      if (NULL == b)
+      {
+        GNUNET_break (0);
+        return GNUNET_NO;
+      }
+      GNUNET_HELLO_builder_iterate (b,
+                                    &pid,
+                                    NULL, NULL);
+      GNUNET_CRYPTO_hash (&pid,
+                          sizeof (pid),
+                          &h_pid);
+      GNUNET_HELLO_builder_free (b);
+      if (0 !=
+          GNUNET_memcmp (&h_pid,
+                         query))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_NO;
+      }
+      return GNUNET_OK;
+    }
+  default:
+    return GNUNET_SYSERR;
   }
-  return GNUNET_OK;
 }
 
 
@@ -251,49 +306,68 @@ block_plugin_dht_check_block (void *cls,
  */
 static enum GNUNET_BLOCK_ReplyEvaluationResult
 block_plugin_dht_check_reply (
-                                        void *cls,
-                                        enum GNUNET_BLOCK_Type type,
-                                    struct GNUNET_BLOCK_Group *group,
-                                    const struct GNUNET_HashCode *query,
-                                    const void *xquery,
-                                    size_t xquery_size,
-                                    const void *reply_block,
-                                    size_t reply_block_size)
+  void *cls,
+  enum GNUNET_BLOCK_Type type,
+  struct GNUNET_BLOCK_Group *group,
+  const struct GNUNET_HashCode *query,
+  const void *xquery,
+  size_t xquery_size,
+  const void *reply_block,
+  size_t reply_block_size)
 {
-  const struct GNUNET_HELLO_Message *hello;
-  struct GNUNET_PeerIdentity pid;
-  const struct GNUNET_MessageHeader *msg;
-  struct GNUNET_HashCode phash;
-
-  if (type != GNUNET_BLOCK_TYPE_DHT_HELLO)
-    return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
-  if (reply_block_size < sizeof(struct GNUNET_MessageHeader))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_REPLY_INVALID;
-  }
-  msg = reply_block;
-  if (reply_block_size != ntohs (msg->size))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_REPLY_INVALID;
-  }
-  hello = reply_block;
-  if (GNUNET_OK !=
-      GNUNET_HELLO_get_id (hello,
-                           &pid))
+  switch (type)
   {
-    GNUNET_break_op (0);
-    return GNUNET_BLOCK_REPLY_INVALID;
+  case GNUNET_BLOCK_TYPE_DHT_HELLO:
+    {
+      const struct GNUNET_HELLO_Message *hello;
+      struct GNUNET_PeerIdentity pid;
+      const struct GNUNET_MessageHeader *msg;
+      struct GNUNET_HashCode phash;
+
+      if (reply_block_size < sizeof(struct GNUNET_MessageHeader))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_REPLY_INVALID;
+      }
+      msg = reply_block;
+      if (reply_block_size != ntohs (msg->size))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_REPLY_INVALID;
+      }
+      hello = reply_block;
+      if (GNUNET_OK !=
+          GNUNET_HELLO_get_id (hello,
+                               &pid))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_BLOCK_REPLY_INVALID;
+      }
+      GNUNET_CRYPTO_hash (&pid,
+                          sizeof(pid),
+                          &phash);
+      if (GNUNET_YES ==
+          GNUNET_BLOCK_GROUP_bf_test_and_set (group,
+                                              &phash))
+        return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
+      return GNUNET_BLOCK_REPLY_OK_MORE;
+    }
+  case GNUNET_BLOCK_TYPE_DHT_URL_HELLO:
+    {
+      struct GNUNET_HashCode phash;
+
+      GNUNET_CRYPTO_hash (reply_block,
+                          reply_block_size,
+                          &phash);
+      if (GNUNET_YES ==
+          GNUNET_BLOCK_GROUP_bf_test_and_set (group,
+                                              &phash))
+        return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
+      return GNUNET_BLOCK_REPLY_OK_MORE;
+    }
+  default:
+    return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
   }
-  GNUNET_CRYPTO_hash (&pid,
-                      sizeof(pid),
-                      &phash);
-  if (GNUNET_YES ==
-      GNUNET_BLOCK_GROUP_bf_test_and_set (group,
-                                          &phash))
-    return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
-  return GNUNET_BLOCK_REPLY_OK_MORE;
 }
 
 
@@ -315,41 +389,68 @@ block_plugin_dht_get_key (void *cls,
                           size_t block_size,
                           struct GNUNET_HashCode *key)
 {
-  const struct GNUNET_MessageHeader *msg;
-  const struct GNUNET_HELLO_Message *hello;
-  struct GNUNET_PeerIdentity *pid;
-
-  if (type != GNUNET_BLOCK_TYPE_DHT_HELLO)
-    return GNUNET_SYSERR;
-  if (block_size < sizeof(struct GNUNET_MessageHeader))
-  {
-    GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
-                     "block-dht",
-                     _ ("Block not of type %u\n"),
-                     GNUNET_BLOCK_TYPE_DHT_HELLO);
-    return GNUNET_NO;
-  }
-  msg = block;
-  if (block_size != ntohs (msg->size))
-  {
-    GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
-                     "block-dht",
-                     _ ("Size mismatch for block with type %u\n"),
-                     GNUNET_BLOCK_TYPE_DHT_HELLO);
-    return GNUNET_NO;
-  }
-  hello = block;
-  memset (key, 0, sizeof(*key));
-  pid = (struct GNUNET_PeerIdentity *) key;
-  if (GNUNET_OK != GNUNET_HELLO_get_id (hello, pid))
+  switch (type)
   {
-    GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
-                     "block-dht",
-                     _ ("Block of type %u is malformed\n"),
-                     GNUNET_BLOCK_TYPE_DHT_HELLO);
-    return GNUNET_NO;
+  case GNUNET_BLOCK_TYPE_DHT_HELLO:
+    {
+      const struct GNUNET_MessageHeader *msg;
+      const struct GNUNET_HELLO_Message *hello;
+      struct GNUNET_PeerIdentity *pid;
+
+      if (block_size < sizeof(struct GNUNET_MessageHeader))
+      {
+        GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
+                         "block-dht",
+                         _ ("Block not of type %u\n"),
+                         GNUNET_BLOCK_TYPE_DHT_HELLO);
+        return GNUNET_NO;
+      }
+      msg = block;
+      if (block_size != ntohs (msg->size))
+      {
+        GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
+                         "block-dht",
+                         _ ("Size mismatch for block with type %u\n"),
+                         GNUNET_BLOCK_TYPE_DHT_HELLO);
+        return GNUNET_NO;
+      }
+      hello = block;
+      memset (key, 0, sizeof(*key));
+      pid = (struct GNUNET_PeerIdentity *) key;
+      if (GNUNET_OK != GNUNET_HELLO_get_id (hello, pid))
+      {
+        GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
+                         "block-dht",
+                         _ ("Block of type %u is malformed\n"),
+                         GNUNET_BLOCK_TYPE_DHT_HELLO);
+        return GNUNET_NO;
+      }
+      return GNUNET_OK;
+    }
+  case GNUNET_BLOCK_TYPE_DHT_URL_HELLO:
+    {
+      struct GNUNET_HELLO_Builder *b;
+      struct GNUNET_PeerIdentity pid;
+
+      b = GNUNET_HELLO_builder_from_block (block,
+                                           block_size);
+      if (NULL == b)
+      {
+        GNUNET_break (0);
+        return GNUNET_NO;
+      }
+      GNUNET_HELLO_builder_iterate (b,
+                                    &pid,
+                                    NULL, NULL);
+      GNUNET_CRYPTO_hash (&pid,
+                          sizeof (pid),
+                          key);
+      GNUNET_HELLO_builder_free (b);
+      return GNUNET_OK;
+    }
+  default:
+    return GNUNET_SYSERR;
   }
-  return GNUNET_OK;
 }
 
 
@@ -361,6 +462,7 @@ libgnunet_plugin_block_dht_init (void *cls)
 {
   static enum GNUNET_BLOCK_Type types[] = {
     GNUNET_BLOCK_TYPE_DHT_HELLO,
+    GNUNET_BLOCK_TYPE_DHT_URL_HELLO,
     GNUNET_BLOCK_TYPE_ANY       /* end of list */
   };
   struct GNUNET_BLOCK_PluginFunctions *api;
diff --git a/src/dhtu/Makefile.am b/src/dhtu/Makefile.am
index 0e10721cd..69b72db6e 100644
--- a/src/dhtu/Makefile.am
+++ b/src/dhtu/Makefile.am
@@ -10,6 +10,10 @@ if USE_COVERAGE
   XLIBS = -lgcov
 endif
 
+pkgcfg_DATA = \
+  dhtu.conf
+
+
 plugin_LTLIBRARIES = \
   libgnunet_plugin_dhtu_gnunet.la \
   libgnunet_plugin_dhtu_ip.la
diff --git a/src/dhtu/dhtu.conf b/src/dhtu/dhtu.conf
new file mode 100644
index 000000000..ea5ade752
--- /dev/null
+++ b/src/dhtu/dhtu.conf
@@ -0,0 +1,7 @@
+[dhtu-gnunet]
+ENABLED = YES
+
+[dhtu-ip]
+ENABLED = NO
+NSE = 4
+UDP_PORT = 6666
diff --git a/src/dhtu/plugin_dhtu_gnunet.c b/src/dhtu/plugin_dhtu_gnunet.c
index 2163af941..fe97f4919 100644
--- a/src/dhtu/plugin_dhtu_gnunet.c
+++ b/src/dhtu/plugin_dhtu_gnunet.c
@@ -69,11 +69,6 @@ struct HelloHandle
 struct GNUNET_DHTU_Source
 {
 
-  /**
-   * Hash of @e pid, position of this peer in the DHT overlay.
-   */
-  struct GNUNET_DHTU_HashKey id;
-
   /**
    * Application context for this source.
    */
@@ -124,11 +119,6 @@ struct GNUNET_DHTU_Target
    */
   struct GNUNET_PeerIdentity pid;
 
-  /**
-   * Hash of @e pid, position of this peer in the DHT overlay.
-   */
-  struct GNUNET_DHTU_HashKey id;
-
   /**
    * Preference counter, length of the @a ph_head DLL.
    */
@@ -240,27 +230,26 @@ hello_offered_cb (void *cls)
  * Request creation of a session with a peer at the given @a address.
  *
  * @param cls closure (internal context for the plugin)
+ * @param pid target identity of the peer to connect to
  * @param address target address to connect to
  */
 static void
-ip_try_connect (void *cls,
-                const char *address)
+gnunet_try_connect (void *cls,
+                    const struct GNUNET_PeerIdentity *pid,
+                    const char *address)
 {
   struct Plugin *plugin = cls;
   struct GNUNET_HELLO_Message *hello = NULL;
   struct HelloHandle *hh;
   struct GNUNET_CRYPTO_EddsaPublicKey pubkey;
 
+  (void) pid; /* will be needed with future address URIs */
   if (GNUNET_OK !=
       GNUNET_HELLO_parse_uri (address,
                               &pubkey,
                               &hello,
                               &GPI_plugins_find))
-  {
-    GNUNET_break (0);
     return;
-  }
-
   hh = GNUNET_new (struct HelloHandle);
   hh->plugin = plugin;
   GNUNET_CONTAINER_DLL_insert (plugin->hh_head,
@@ -283,8 +272,8 @@ ip_try_connect (void *cls,
  * @param target connection to keep alive
  */
 static struct GNUNET_DHTU_PreferenceHandle *
-ip_hold (void *cls,
-         struct GNUNET_DHTU_Target *target)
+gnunet_hold (void *cls,
+             struct GNUNET_DHTU_Target *target)
 {
   struct Plugin *plugin = cls;
   struct GNUNET_DHTU_PreferenceHandle *ph;
@@ -312,7 +301,7 @@ ip_hold (void *cls,
  * @param target connection to keep alive
  */
 static void
-ip_drop (struct GNUNET_DHTU_PreferenceHandle *ph)
+gnunet_drop (struct GNUNET_DHTU_PreferenceHandle *ph)
 {
   struct GNUNET_DHTU_Target *target = ph->target;
   struct Plugin *plugin = target->plugin;
@@ -350,12 +339,12 @@ ip_drop (struct GNUNET_DHTU_PreferenceHandle *ph)
  * @param finished_cb_cls closure for @a finished_cb
  */
 static void
-ip_send (void *cls,
-         struct GNUNET_DHTU_Target *target,
-         const void *msg,
-         size_t msg_size,
-         GNUNET_SCHEDULER_TaskCallback finished_cb,
-         void *finished_cb_cls)
+gnunet_send (void *cls,
+             struct GNUNET_DHTU_Target *target,
+             const void *msg,
+             size_t msg_size,
+             GNUNET_SCHEDULER_TaskCallback finished_cb,
+             void *finished_cb_cls)
 {
   struct GNUNET_MQ_Envelope *env;
   struct GNUNET_MessageHeader *cmsg;
@@ -394,12 +383,9 @@ core_connect_cb (void *cls,
   target->plugin = plugin;
   target->mq = mq;
   target->pid = *peer;
-  GNUNET_CRYPTO_hash (peer,
-                      sizeof (*peer),
-                      &target->id.sha512);
   plugin->env->connect_cb (plugin->env->cls,
                            target,
-                           &target->id,
+                           &target->pid,
                            &target->app_ctx);
   return target;
 }
@@ -461,11 +447,7 @@ peerinfo_cb (void *cls,
                                    &GPI_plugins_find);
   if (NULL == addr)
     return;
-  GNUNET_CRYPTO_hash (peer,
-                      sizeof (*peer),
-                      &plugin->src.id.sha512);
   plugin->env->address_add_cb (plugin->env->cls,
-                               &plugin->src.id,
                                addr,
                                &plugin->src,
                                &plugin->src.app_ctx);
@@ -584,6 +566,10 @@ libgnunet_plugin_dhtu_gnunet_done (void *cls)
   }
   if (NULL != plugin->nse)
     GNUNET_NSE_disconnect (plugin->nse);
+  plugin->env->network_size_cb (plugin->env->cls,
+                                GNUNET_TIME_UNIT_FOREVER_ABS,
+                                0.0,
+                                0.0);
   if (NULL != plugin->core)
     GNUNET_CORE_disconnect (plugin->core);
   if (NULL != plugin->ats)
@@ -604,7 +590,7 @@ libgnunet_plugin_dhtu_gnunet_done (void *cls)
  * @return the plugin's API
  */
 void *
-libgnunet_plugin_dhtu_ip_init (void *cls)
+libgnunet_plugin_dhtu_gnunet_init (void *cls)
 {
   struct GNUNET_DHTU_PluginEnvironment *env = cls;
   struct GNUNET_DHTU_PluginFunctions *api;
@@ -621,10 +607,10 @@ libgnunet_plugin_dhtu_ip_init (void *cls)
   plugin->env = env;
   api = GNUNET_new (struct GNUNET_DHTU_PluginFunctions);
   api->cls = plugin;
-  api->try_connect = &ip_try_connect;
-  api->hold = &ip_hold;
-  api->drop = &ip_drop;
-  api->send = &ip_send;
+  api->try_connect = &gnunet_try_connect;
+  api->hold = &gnunet_hold;
+  api->drop = &gnunet_drop;
+  api->send = &gnunet_send;
   plugin->ats = GNUNET_ATS_connectivity_init (env->cfg);
   plugin->core = GNUNET_CORE_connect (env->cfg,
                                       plugin,
diff --git a/src/dhtu/plugin_dhtu_ip.c b/src/dhtu/plugin_dhtu_ip.c
index 8eec6294b..612d2c119 100644
--- a/src/dhtu/plugin_dhtu_ip.c
+++ b/src/dhtu/plugin_dhtu_ip.c
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet
-     Copyright (C) 2021 GNUnet e.V.
+     Copyright (C) 2021, 2022 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
@@ -55,18 +55,13 @@ struct GNUNET_DHTU_Source
    */
   struct GNUNET_DHTU_Source *prev;
 
-  /**
-   * Position of this peer in the DHT.
-   */
-  struct GNUNET_DHTU_HashKey id;
-
   /**
    * Application context for this source.
    */
   void *app_ctx;
 
   /**
-   * Address in URL form ("ip+udp://$IP:$PORT")
+   * Address in URL form ("ip+udp://$PID/$IP:$PORT")
    */
   char *address;
 
@@ -121,9 +116,9 @@ struct GNUNET_DHTU_Target
   struct GNUNET_DHTU_PreferenceHandle *ph_tail;
 
   /**
-   * Position of this peer in the DHT.
+   * Peer's identity.
    */
-  struct GNUNET_DHTU_HashKey id;
+  struct GNUNET_PeerIdentity pid;
 
   /**
    * Target IP address.
@@ -216,15 +211,25 @@ struct Plugin
    */
   char *port;
 
+  /**
+   * My UDP socket.
+   */
+  struct GNUNET_NETWORK_Handle *sock;
+
+  /**
+   * My identity.
+   */
+  struct GNUNET_PeerIdentity my_id;
+
   /**
    * How often have we scanned for IPs?
    */
   unsigned int scan_generation;
 
   /**
-   * My UDP socket.
+   * Port as a 16-bit value.
    */
-  struct GNUNET_NETWORK_Handle *sock;
+  uint16_t port16;
 };
 
 
@@ -232,18 +237,20 @@ struct Plugin
  * Create a target to which we may send traffic.
  *
  * @param plugin our plugin
+ * @param pid presumed identity of the target
  * @param addr target address
  * @param addrlen number of bytes in @a addr
  * @return new target object
  */
 static struct GNUNET_DHTU_Target *
 create_target (struct Plugin *plugin,
+               const struct GNUNET_PeerIdentity *pid,
                const struct sockaddr *addr,
                socklen_t addrlen)
 {
   struct GNUNET_DHTU_Target *dst;
 
-  if (MAX_DESTS >
+  if (MAX_DESTS <=
       GNUNET_CONTAINER_multihashmap_size (plugin->dsts))
   {
     struct GNUNET_HashCode key;
@@ -275,42 +282,16 @@ create_target (struct Plugin *plugin,
   }
   dst = GNUNET_new (struct GNUNET_DHTU_Target);
   dst->addrlen = addrlen;
+  dst->pid = *pid;
   memcpy (&dst->addr,
           addr,
           addrlen);
-  switch (addr->sa_family)
-  {
-  case AF_INET:
-    {
-      const struct sockaddr_in *s4 = (const struct sockaddr_in *) addr;
-
-      GNUNET_assert (sizeof (struct sockaddr_in) == addrlen);
-      GNUNET_CRYPTO_hash (&s4->sin_addr,
-                          sizeof (struct in_addr),
-                          &dst->id.sha512);
-    }
-    break;
-  case AF_INET6:
-    {
-      const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *) addr;
-
-      GNUNET_assert (sizeof (struct sockaddr_in6) == addrlen);
-      GNUNET_CRYPTO_hash (&s6->sin6_addr,
-                          sizeof (struct in6_addr),
-                          &dst->id.sha512);
-    }
-    break;
-  default:
-    GNUNET_break (0);
-    GNUNET_free (dst);
-    return NULL;
-  }
   GNUNET_CONTAINER_DLL_insert (plugin->dst_head,
                                plugin->dst_tail,
                                dst);
   plugin->env->connect_cb (plugin->env->cls,
                            dst,
-                           &dst->id,
+                           &dst->pid,
                            &dst->app_ctx);
   return dst;
 }
@@ -321,6 +302,7 @@ create_target (struct Plugin *plugin,
  * create one!
  *
  * @param plugin the plugin handle
+ * @param pid presumed identity of the target
  * @param src source target is from, or NULL if unknown
  * @param addr socket address to find
  * @param addrlen number of bytes in @a addr
@@ -328,6 +310,7 @@ create_target (struct Plugin *plugin,
  */
 static struct GNUNET_DHTU_Target *
 find_target (struct Plugin *plugin,
+             const struct GNUNET_PeerIdentity *pid,
              const void *addr,
              size_t addrlen)
 {
@@ -342,6 +325,7 @@ find_target (struct Plugin *plugin,
   if (NULL == dst)
   {
     dst = create_target (plugin,
+                         pid,
                          (const struct sockaddr *) addr,
                          addrlen);
     GNUNET_assert (GNUNET_YES ==
@@ -370,10 +354,12 @@ find_target (struct Plugin *plugin,
  * Request creation of a session with a peer at the given @a address.
  *
  * @param cls closure (internal context for the plugin)
+ * @param pid identity of the target peer
  * @param address target address to connect to
  */
 static void
 ip_try_connect (void *cls,
+                const struct GNUNET_PeerIdentity *pid,
                 const char *address)
 {
   struct Plugin *plugin = cls;
@@ -389,19 +375,13 @@ ip_try_connect (void *cls,
       strncmp (address,
                "ip+",
                strlen ("ip+")))
-  {
-    GNUNET_break (0);
     return;
-  }
   address += strlen ("ip+");
   if (0 !=
       strncmp (address,
                "udp://",
                strlen ("udp://")))
-  {
-    GNUNET_break (0);
     return;
-  }
   address += strlen ("udp://");
   addr = GNUNET_strdup (address);
   colon = strchr (addr, ':');
@@ -426,6 +406,7 @@ ip_try_connect (void *cls,
   }
   GNUNET_free (addr);
   (void) find_target (plugin,
+                      pid,
                       result->ai_addr,
                       result->ai_addrlen);
   freeaddrinfo (result);
@@ -499,10 +480,17 @@ ip_send (void *cls,
          void *finished_cb_cls)
 {
   struct Plugin *plugin = cls;
-
+  char buf[sizeof (plugin->my_id) + msg_size];
+
+  memcpy (buf,
+          &plugin->my_id,
+          sizeof (plugin->my_id));
+  memcpy (&buf[sizeof (plugin->my_id)],
+          msg,
+          msg_size);
   GNUNET_NETWORK_socket_sendto (plugin->sock,
-                                msg,
-                                msg_size,
+                                buf,
+                                sizeof (buf),
                                 (const struct sockaddr *) &target->addr,
                                 target->addrlen);
   finished_cb (finished_cb_cls);
@@ -538,9 +526,6 @@ create_source (struct Plugin *plugin,
       char buf[INET_ADDRSTRLEN];
 
       GNUNET_assert (sizeof (struct sockaddr_in) == addrlen);
-      GNUNET_CRYPTO_hash (&s4->sin_addr,
-                          sizeof (struct in_addr),
-                          &src->id.sha512);
       GNUNET_asprintf (&src->address,
                        "ip+udp://%s:%u",
                        inet_ntop (AF_INET,
@@ -556,9 +541,6 @@ create_source (struct Plugin *plugin,
       char buf[INET6_ADDRSTRLEN];
 
       GNUNET_assert (sizeof (struct sockaddr_in6) == addrlen);
-      GNUNET_CRYPTO_hash (&s6->sin6_addr,
-                          sizeof (struct in6_addr),
-                          &src->id.sha512);
       GNUNET_asprintf (&src->address,
                        "ip+udp://[%s]:%u",
                        inet_ntop (AF_INET6,
@@ -577,7 +559,6 @@ create_source (struct Plugin *plugin,
                                plugin->src_tail,
                                src);
   plugin->env->address_add_cb (plugin->env->cls,
-                               &src->id,
                                src->address,
                                src,
                                &src->app_ctx);
@@ -585,6 +566,101 @@ create_source (struct Plugin *plugin,
 }
 
 
+/**
+ * Compare two addresses excluding the ports for equality. Only compares IP
+ * address. Must only be called on AF_INET or AF_INET6 addresses.
+ *
+ * @param a1 address to compare
+ * @param a2 address to compare
+ * @param alen number of bytes in @a a1 and @a a2
+ * @return 0 if @a a1 == @a a2.
+ */
+static int
+addrcmp_np (const struct sockaddr *a1,
+            const struct sockaddr *a2,
+            size_t alen)
+{
+  GNUNET_assert (a1->sa_family == a2->sa_family);
+  switch (a1->sa_family)
+  {
+  case AF_INET:
+    GNUNET_assert (sizeof (struct sockaddr_in) == alen);
+    {
+      const struct sockaddr_in *s1 = (const struct sockaddr_in *) a1;
+      const struct sockaddr_in *s2 = (const struct sockaddr_in *) a2;
+
+      if (s1->sin_addr.s_addr != s2->sin_addr.s_addr)
+        return 1;
+      break;
+    }
+  case AF_INET6:
+    GNUNET_assert (sizeof (struct sockaddr_in6) == alen);
+    {
+      const struct sockaddr_in6 *s1 = (const struct sockaddr_in6 *) a1;
+      const struct sockaddr_in6 *s2 = (const struct sockaddr_in6 *) a2;
+
+      if (0 != GNUNET_memcmp (&s1->sin6_addr,
+                              &s2->sin6_addr))
+        return 1;
+      break;
+    }
+  default:
+    GNUNET_assert (0);
+  }
+  return 0;
+}
+
+
+/**
+ * Compare two addresses for equality. Only
+ * compares IP address and port. Must only be
+ * called on AF_INET or AF_INET6 addresses.
+ *
+ * @param a1 address to compare
+ * @param a2 address to compare
+ * @param alen number of bytes in @a a1 and @a a2
+ * @return 0 if @a a1 == @a a2.
+ */
+static int
+addrcmp (const struct sockaddr *a1,
+         const struct sockaddr *a2,
+         size_t alen)
+{
+  GNUNET_assert (a1->sa_family == a2->sa_family);
+  switch (a1->sa_family)
+  {
+  case AF_INET:
+    GNUNET_assert (sizeof (struct sockaddr_in) == alen);
+    {
+      const struct sockaddr_in *s1 = (const struct sockaddr_in *) a1;
+      const struct sockaddr_in *s2 = (const struct sockaddr_in *) a2;
+
+      if (s1->sin_port != s2->sin_port)
+        return 1;
+      if (s1->sin_addr.s_addr != s2->sin_addr.s_addr)
+        return 1;
+      break;
+    }
+  case AF_INET6:
+    GNUNET_assert (sizeof (struct sockaddr_in6) == alen);
+    {
+      const struct sockaddr_in6 *s1 = (const struct sockaddr_in6 *) a1;
+      const struct sockaddr_in6 *s2 = (const struct sockaddr_in6 *) a2;
+
+      if (s1->sin6_port != s2->sin6_port)
+        return 1;
+      if (0 != GNUNET_memcmp (&s1->sin6_addr,
+                              &s2->sin6_addr))
+        return 1;
+      break;
+    }
+  default:
+    GNUNET_assert (0);
+  }
+  return 0;
+}
+
+
 /**
  * Callback function invoked for each interface found.
  *
@@ -597,7 +673,7 @@ create_source (struct Plugin *plugin,
  * @param addrlen length of the address
  * @return #GNUNET_OK to continue iteration, #GNUNET_SYSERR to abort
  */
-static int
+static enum GNUNET_GenericReturnValue
 process_ifcs (void *cls,
               const char *name,
               int isDefault,
@@ -614,17 +690,45 @@ process_ifcs (void *cls,
        src = src->next)
   {
     if ( (addrlen == src->addrlen) &&
-         (0 == memcmp (addr,
-                       &src->addr,
-                       addrlen)) )
+         (0 == addrcmp_np (addr,
+                           (const struct sockaddr *) &src->addr,
+                           addrlen)) )
     {
       src->scan_generation = plugin->scan_generation;
       return GNUNET_OK;
     }
   }
-  (void) create_source (plugin,
-                        addr,
-                        addrlen);
+  switch (addr->sa_family)
+  {
+  case AF_INET:
+    {
+      struct sockaddr_in v4;
+
+      GNUNET_assert (sizeof(v4) == addrlen);
+      memcpy (&v4,
+              addr,
+              addrlen);
+      v4.sin_port = htons (plugin->port16);
+      (void) create_source (plugin,
+                            (const struct sockaddr *) &v4,
+                            sizeof (v4));
+      break;
+    }
+  case AF_INET6:
+    {
+      struct sockaddr_in6 v6;
+
+      GNUNET_assert (sizeof(v6) == addrlen);
+      memcpy (&v6,
+              addr,
+              addrlen);
+      v6.sin6_port = htons (plugin->port16);
+      (void) create_source (plugin,
+                            (const struct sockaddr *) &v6,
+                            sizeof (v6));
+      break;
+    }
+  }
   return GNUNET_OK;
 }
 
@@ -648,7 +752,7 @@ scan (void *cls)
        src = next)
   {
     next = src->next;
-    if (src->scan_generation == plugin->scan_generation)
+    if (src->scan_generation >= plugin->scan_generation)
       continue;
     GNUNET_CONTAINER_DLL_remove (plugin->src_head,
                                  plugin->src_tail,
@@ -682,9 +786,9 @@ find_source (struct Plugin *plugin,
        src = src->next)
   {
     if ( (addrlen == src->addrlen) &&
-         (0 == memcmp (addr,
-                       &src->addr,
-                       addrlen)) )
+         (0 == addrcmp (addr,
+                        (const struct sockaddr *) &src->addr,
+                        addrlen)) )
       return src;
   }
 
@@ -704,7 +808,8 @@ read_cb (void *cls)
 {
   struct Plugin *plugin = cls;
   ssize_t ret;
-  char buf[65536];
+  const struct GNUNET_PeerIdentity *pid;
+  char buf[65536] GNUNET_ALIGN;
   struct sockaddr_storage sa;
   struct iovec iov = {
     .iov_base = buf,
@@ -719,98 +824,120 @@ read_cb (void *cls)
     .msg_control = ctl,
     .msg_controllen = sizeof (ctl)
   };
+  struct GNUNET_DHTU_Target *dst = NULL;
+  struct GNUNET_DHTU_Source *src = NULL;
 
   ret = recvmsg  (GNUNET_NETWORK_get_fd (plugin->sock),
                   &mh,
                   MSG_DONTWAIT);
-  if (ret >= 0)
+  plugin->read_task = GNUNET_SCHEDULER_add_read_net (
+    GNUNET_TIME_UNIT_FOREVER_REL,
+    plugin->sock,
+    &read_cb,
+    plugin);
+  if (ret < 0)
+    return; /* read failure, hopefully EAGAIN */
+  if (ret < sizeof (*pid))
+  {
+    GNUNET_break_op (0);
+    return;
+  }
+  /* find IP where we received message */
+  for (struct cmsghdr *cmsg = CMSG_FIRSTHDR (&mh);
+       NULL != cmsg;
+       cmsg = CMSG_NXTHDR (&mh,
+                           cmsg))
   {
-    struct GNUNET_DHTU_Target *dst = NULL;
-    struct GNUNET_DHTU_Source *src = NULL;
-    struct cmsghdr *cmsg;
-
-    /* find IP where we received message */
-    for (cmsg = CMSG_FIRSTHDR (&mh);
-         NULL != cmsg;
-         cmsg = CMSG_NXTHDR (&mh,
-                             cmsg))
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Got CMSG level %u (%d/%d), type %u (%d/%d)\n",
+                cmsg->cmsg_level,
+                (cmsg->cmsg_level == IPPROTO_IP),
+                (cmsg->cmsg_level == IPPROTO_IPV6),
+                cmsg->cmsg_type,
+                (cmsg->cmsg_type == IP_PKTINFO),
+                (cmsg->cmsg_type == IPV6_PKTINFO));
+    if ( (cmsg->cmsg_level == IPPROTO_IP) &&
+         (cmsg->cmsg_type == IP_PKTINFO) )
     {
-      if ( (cmsg->cmsg_level == IPPROTO_IP) &&
-           (cmsg->cmsg_type == IP_PKTINFO) )
+      if (CMSG_LEN (sizeof (struct in_pktinfo)) ==
+          cmsg->cmsg_len)
       {
-        if (CMSG_LEN (sizeof (struct in_pktinfo)) ==
-            cmsg->cmsg_len)
+        struct in_pktinfo pi;
+
+        memcpy (&pi,
+                CMSG_DATA (cmsg),
+                sizeof (pi));
         {
-          struct in_pktinfo pi;
-
-          memcpy (&pi,
-                  CMSG_DATA (cmsg),
-                  sizeof (pi));
-          {
-            struct sockaddr_in sa = {
-              .sin_family = AF_INET,
-              .sin_addr = pi.ipi_addr
-            };
-
-            src = find_source (plugin,
-                               &sa,
-                               sizeof (sa));
-          }
-          break;
+          struct sockaddr_in sa = {
+            .sin_family = AF_INET,
+            .sin_addr = pi.ipi_addr,
+            .sin_port = htons (plugin->port16)
+          };
+
+          src = find_source (plugin,
+                             &sa,
+                             sizeof (sa));
+          /* For sources we discovered by reading,
+             force the generation far into the future */
+          src->scan_generation = plugin->scan_generation + 60;
         }
-        else
-          GNUNET_break (0);
+        break;
       }
-      if ( (cmsg->cmsg_level == IPPROTO_IPV6) &&
-           (cmsg->cmsg_type == IPV6_RECVPKTINFO) )
+      else
+        GNUNET_break (0);
+    }
+    if ( (cmsg->cmsg_level == IPPROTO_IPV6) &&
+         (cmsg->cmsg_type == IPV6_PKTINFO) )
+    {
+      if (CMSG_LEN (sizeof (struct in6_pktinfo)) ==
+          cmsg->cmsg_len)
       {
-        if (CMSG_LEN (sizeof (struct in6_pktinfo)) ==
-            cmsg->cmsg_len)
+        struct in6_pktinfo pi;
+
+        memcpy (&pi,
+                CMSG_DATA (cmsg),
+                sizeof (pi));
         {
-          struct in6_pktinfo pi;
-
-          memcpy (&pi,
-                  CMSG_DATA (cmsg),
-                  sizeof (pi));
-          {
-            struct sockaddr_in6 sa = {
-              .sin6_family = AF_INET6,
-              .sin6_addr = pi.ipi6_addr,
-              .sin6_scope_id = pi.ipi6_ifindex
-            };
-
-            src = find_source (plugin,
-                               &sa,
-                               sizeof (sa));
-            break;
-          }
+          struct sockaddr_in6 sa = {
+            .sin6_family = AF_INET6,
+            .sin6_addr = pi.ipi6_addr,
+            .sin6_port = htons (plugin->port16),
+            .sin6_scope_id = pi.ipi6_ifindex
+          };
+
+          src = find_source (plugin,
+                             &sa,
+                             sizeof (sa));
+          /* For sources we discovered by reading,
+             force the generation far into the future */
+          src->scan_generation = plugin->scan_generation + 60;
+          break;
         }
-        else
-          GNUNET_break (0);
       }
-    }
-    dst = find_target (plugin,
-                       &sa,
-                       mh.msg_namelen);
-    if ( (NULL == src) ||
-         (NULL == dst) )
-    {
-      GNUNET_break (0);
-    }
-    else
-    {
-      plugin->env->receive_cb (plugin->env->cls,
-                               dst->app_ctx,
-                               src->app_ctx,
-                               buf,
-                               ret);
+      else
+        GNUNET_break (0);
     }
   }
-  plugin->read_task = GNUNET_SCHEDULER_add_read_net (
-    GNUNET_TIME_UNIT_FOREVER_REL,
-    plugin->sock,
-    &read_cb,
-    plugin);
+  if (NULL == src)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  pid = (const struct GNUNET_PeerIdentity *) buf;
+  dst = find_target (plugin,
+                     pid,
+                     &sa,
+                     mh.msg_namelen);
+  if (NULL == dst)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  plugin->env->receive_cb (plugin->env->cls,
+                           &dst->app_ctx,
+                           &src->app_ctx,
+                           &buf[sizeof(*pid)],
+                           ret - sizeof (*pid));
 }
 
 
@@ -874,6 +1001,14 @@ libgnunet_plugin_dhtu_ip_init (void *cls)
   plugin = GNUNET_new (struct Plugin);
   plugin->env = env;
   plugin->port = port;
+  plugin->port16 = (uint16_t) nport;
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_get_peer_identity (env->cfg,
+                                       &plugin->my_id))
+  {
+    GNUNET_free (plugin);
+    return NULL;
+  }
   af = AF_INET6;
   sock = socket (af,
                  SOCK_DGRAM,
@@ -1017,9 +1152,18 @@ libgnunet_plugin_dhtu_ip_done (void *cls)
     GNUNET_free (src->address);
     GNUNET_free (src);
   }
+  plugin->env->network_size_cb (plugin->env->cls,
+                                GNUNET_TIME_UNIT_FOREVER_ABS,
+                                0.0,
+                                0.0);
   GNUNET_CONTAINER_multihashmap_destroy (plugin->dsts);
+  if (NULL != plugin->read_task)
+  {
+    GNUNET_SCHEDULER_cancel (plugin->read_task);
+    plugin->read_task = NULL;
+  }
   GNUNET_SCHEDULER_cancel (plugin->scan_task);
-  GNUNET_break (0 ==
+  GNUNET_break (GNUNET_OK ==
                 GNUNET_NETWORK_socket_close (plugin->sock));
   GNUNET_free (plugin->port);
   GNUNET_free (plugin);
diff --git a/src/gnsrecord/gnunet-gnsrecord-tvg.c 
b/src/gnsrecord/gnunet-gnsrecord-tvg.c
index 31a2c5da2..112a06a7d 100644
--- a/src/gnsrecord/gnunet-gnsrecord-tvg.c
+++ b/src/gnsrecord/gnunet-gnsrecord-tvg.c
@@ -40,7 +40,9 @@ static char *d_pkey =
 static char *d_edkey =
   "5af7020ee19160328832352bbc6a68a8d71a7cbe1b929969a7c66d415a0d8f65";
 
-int parsehex (char *src, char *dst, size_t dstlen, int invert)
+
+static int
+parsehex (char *src, char *dst, size_t dstlen, int invert)
 {
   char *line = src;
   char *data = line;
@@ -80,6 +82,7 @@ print_bytes_ (void *buf,
   printf ("\n");
 }
 
+
 static void
 print_bytes (void *buf,
              size_t buf_len,
@@ -92,7 +95,7 @@ print_bytes (void *buf,
 static void
 print_record (const struct GNUNET_GNSRECORD_Data *rd)
 {
-  uint16_t flags = htons(rd->flags);
+  uint16_t flags = htons (rd->flags);
   fprintf (stdout,
            "EXPIRATION: %" PRIu64 "\n", rd->expiration_time);
   fprintf (stdout,
@@ -101,7 +104,7 @@ print_record (const struct GNUNET_GNSRECORD_Data *rd)
            "TYPE: %d\n", rd->record_type);
   fprintf (stdout,
            "FLAGS: ");
-  print_bytes ((void*)&flags, sizeof (flags), 8);
+  print_bytes ((void*) &flags, sizeof (flags), 8);
   printf ("\n");
   fprintf (stdout,
            "DATA:\n");
@@ -122,9 +125,6 @@ static void
 run_pkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, const char *label)
 {
   struct GNUNET_TIME_Absolute expire;
-  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
-  struct GNUNET_TIME_Relative delta1;
-  struct GNUNET_TIME_Relative delta2;
   struct GNUNET_GNSRECORD_Block *rrblock;
   char *bdata;
   struct GNUNET_IDENTITY_PrivateKey id_priv;
@@ -132,16 +132,12 @@ run_pkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, 
const char *label)
   struct GNUNET_IDENTITY_PrivateKey pkey_data_p;
   struct GNUNET_IDENTITY_PublicKey pkey_data;
   struct GNUNET_HashCode query;
-  void *data;
-  size_t data_size;
   char *rdata;
   size_t rdata_size;
-  uint32_t rd_count_nbo;
   char ztld[128];
   unsigned char ctr[GNUNET_CRYPTO_AES_KEY_LENGTH / 2];
   unsigned char skey[GNUNET_CRYPTO_AES_KEY_LENGTH];
 
-
   id_priv.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY);
   GNUNET_CRYPTO_ecdsa_key_create (&id_priv.ecdsa_key);
   parsehex (d_pkey,
@@ -179,16 +175,17 @@ run_pkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, 
const char *label)
     print_record (&rd[i]);
   }
 
-
   rdata_size = GNUNET_GNSRECORD_records_get_size (rd_count,
                                                   rd);
   rdata = GNUNET_malloc (rdata_size);
   GNUNET_GNSRECORD_records_serialize (rd_count,
                                       rd,
-                                      rdata_size,
+                                      (size_t) rdata_size,
                                       rdata);
   fprintf (stdout, "RDATA:\n");
-  print_bytes (rdata, rdata_size, 8);
+  print_bytes (rdata,
+               (size_t) rdata_size,
+               8);
   fprintf (stdout, "\n");
   expire = GNUNET_GNSRECORD_record_get_expiration_time (rd_count, rd,
                                                         
GNUNET_TIME_UNIT_ZERO_ABS);
@@ -217,14 +214,15 @@ run_pkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, 
const char *label)
                                                              rd,
                                                              rd_count,
                                                              &rrblock));
-  size_t bdata_size = ntohl(rrblock->size) - sizeof (struct 
GNUNET_GNSRECORD_Block);
+  size_t bdata_size = ntohl (rrblock->size) - sizeof (struct
+                                                      GNUNET_GNSRECORD_Block);
 
   bdata = (char*) &(&rrblock->ecdsa_block)[1];
   fprintf (stdout, "BDATA:\n");
   print_bytes (bdata, bdata_size, 8);
   fprintf (stdout, "\n");
   fprintf (stdout, "RRBLOCK:\n");
-  print_bytes (rrblock, ntohl(rrblock->size), 8);
+  print_bytes (rrblock, ntohl (rrblock->size), 8);
   fprintf (stdout, "\n");
   GNUNET_free (rdata);
 }
@@ -239,12 +237,9 @@ run_pkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, 
const char *label)
  * @param cfg configuration
  */
 static void
-run_edkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, const char* label)
+run_edkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, const char*label)
 {
   struct GNUNET_TIME_Absolute expire;
-  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
-  struct GNUNET_TIME_Relative delta1;
-  struct GNUNET_TIME_Relative delta2;
   struct GNUNET_GNSRECORD_Block *rrblock;
   char *bdata;
   struct GNUNET_IDENTITY_PrivateKey id_priv;
@@ -252,11 +247,9 @@ run_edkey (struct GNUNET_GNSRECORD_Data *rd, int rd_count, 
const char* label)
   struct GNUNET_IDENTITY_PrivateKey pkey_data_p;
   struct GNUNET_IDENTITY_PublicKey pkey_data;
   struct GNUNET_HashCode query;
-  void *data;
-  size_t data_size;
   char *rdata;
   size_t rdata_size;
-  uint32_t rd_count_nbo;
+
   char ztld[128];
   unsigned char nonce[crypto_secretbox_NONCEBYTES];
   unsigned char skey[crypto_secretbox_KEYBYTES];
@@ -307,13 +300,16 @@ run_edkey (struct GNUNET_GNSRECORD_Data *rd, int 
rd_count, const char* label)
   expire = GNUNET_GNSRECORD_record_get_expiration_time (rd_count,
                                                         rd,
                                                         
GNUNET_TIME_UNIT_ZERO_ABS);
-  rdata = GNUNET_malloc (rdata_size);
+  GNUNET_assert (0 < rdata_size);
+  rdata = GNUNET_malloc ((size_t) rdata_size);
   GNUNET_GNSRECORD_records_serialize (rd_count,
                                       rd,
-                                      rdata_size,
+                                      (size_t) rdata_size,
                                       rdata);
   fprintf (stdout, "RDATA:\n");
-  print_bytes (rdata, rdata_size, 8);
+  print_bytes (rdata,
+               (size_t) rdata_size,
+               8);
   fprintf (stdout, "\n");
   GNR_derive_block_xsalsa_key (nonce,
                                skey,
@@ -340,14 +336,15 @@ run_edkey (struct GNUNET_GNSRECORD_Data *rd, int 
rd_count, const char* label)
                                                               rd,
                                                               rd_count,
                                                               &rrblock));
-  size_t bdata_size = ntohl(rrblock->size) - sizeof (struct 
GNUNET_GNSRECORD_Block);
+  size_t bdata_size = ntohl (rrblock->size) - sizeof (struct
+                                                      GNUNET_GNSRECORD_Block);
 
   bdata = (char*) &(&rrblock->eddsa_block)[1];
   fprintf (stdout, "BDATA:\n");
   print_bytes (bdata, bdata_size, 8);
   fprintf (stdout, "\n");
   fprintf (stdout, "RRBLOCK:\n");
-  print_bytes (rrblock, ntohl(rrblock->size), 8);
+  print_bytes (rrblock, ntohl (rrblock->size), 8);
   fprintf (stdout, "\n");
   GNUNET_free (rdata);
 }
@@ -377,7 +374,6 @@ run (void *cls,
   char *pkey_data;
   char *ip_data;
 
-
   /*
    * Make different expiration times
    */
@@ -389,12 +385,11 @@ run (void *cls,
                                          &exp3);
 
 
-
   memset (&rd_pkey, 0, sizeof (struct GNUNET_GNSRECORD_Data));
   GNUNET_assert (GNUNET_OK == GNUNET_GNSRECORD_string_to_value (
                    GNUNET_GNSRECORD_TYPE_PKEY,
                    
"000G0011WESGZY9VRV9NNJ66W3GKNZFZF56BFD2BQF3MHMJST2G2GKDYGG",
-                   (void**)&pkey_data,
+                   (void**) &pkey_data,
                    &pkey_data_size));
   rd_pkey.data = pkey_data;
   rd_pkey.data_size = pkey_data_size;
@@ -404,7 +399,7 @@ run (void *cls,
   GNUNET_assert (GNUNET_OK == GNUNET_GNSRECORD_string_to_value (
                    GNUNET_DNSPARSER_TYPE_AAAA,
                    "::dead:beef",
-                   (void**)&ip_data,
+                   (void**) &ip_data,
                    &ip_data_size));
 
   rd[0].data = ip_data;
@@ -423,17 +418,13 @@ run (void *cls,
   rd[2].data_size = strlen (rd[2].data);
   rd[2].expiration_time = exp3.rel_value_us;
   rd[2].record_type = GNUNET_DNSPARSER_TYPE_TXT;
-  rd[2].flags = GNUNET_GNSRECORD_RF_SUPPLEMENTAL | 
GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
+  rd[2].flags = GNUNET_GNSRECORD_RF_SUPPLEMENTAL
+                | GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
 
   run_pkey (&rd_pkey, 1, "testdelegation");
   run_pkey (rd, 3, "namesystem");
   run_edkey (&rd_pkey, 1, "testdelegation");
   run_edkey (rd, 3, "namesystem");
-  /*char *norm_lbl;
-  norm_lbl = GNUNET_GNSRECORD_string_normalize ("q\u0307\u0323namesysteM");
-  print_bytes ("q\u0307\u0323namesysteM", strlen ("q\u0307\u0323namesysteM"), 
8);
-  print_bytes (norm_lbl, strlen (norm_lbl), 8);
-  printf ("%s\n", norm_lbl);*/
 }
 
 
diff --git a/src/hello/.gitignore b/src/hello/.gitignore
index bb49ceb20..d175d148e 100644
--- a/src/hello/.gitignore
+++ b/src/hello/.gitignore
@@ -1,3 +1,5 @@
 gnunet-hello
 test_friend_hello
 test_hello
+test_hello-uri
+test_hello-ng
diff --git a/src/hello/Makefile.am b/src/hello/Makefile.am
index 6a250e42f..c04b85106 100644
--- a/src/hello/Makefile.am
+++ b/src/hello/Makefile.am
@@ -11,7 +11,8 @@ lib_LTLIBRARIES = libgnunethello.la
 libgnunethello_la_SOURCES = \
   hello.c \
   address.c \
-  hello-ng.c
+  hello-ng.c \
+  hello-uri.c
 libgnunethello_la_LIBADD = \
  $(top_builddir)/src/util/libgnunetutil.la $(XLIB) \
  $(LTLIBINTL)
@@ -24,6 +25,7 @@ noinst_PROGRAMS = \
 
 check_PROGRAMS = \
  test_hello \
+ test_hello-uri \
  test_friend_hello \
  test_hello-ng
 
@@ -36,25 +38,32 @@ test_hello_SOURCES = \
  test_hello.c
 test_hello_LDADD = \
  libgnunethello.la \
- $(top_builddir)/src/util/libgnunetutil.la  
+ $(top_builddir)/src/util/libgnunetutil.la
 
 test_hello_ng_SOURCES = \
  test_hello-ng.c
 test_hello_ng_LDADD = \
  libgnunethello.la \
- $(top_builddir)/src/util/libgnunetutil.la  
+ $(top_builddir)/src/util/libgnunetutil.la
+
+test_hello_uri_SOURCES = \
+ test_hello-uri.c
+test_hello_uri_LDADD = \
+ libgnunethello.la \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ -lgcrypt
 
 
 test_friend_hello_SOURCES = \
  test_friend_hello.c
 test_friend_hello_LDADD = \
  libgnunethello.la \
- $(top_builddir)/src/util/libgnunetutil.la  
+ $(top_builddir)/src/util/libgnunetutil.la
 
 gnunet_hello_SOURCES = \
  gnunet-hello.c
 gnunet_hello_LDADD = \
  libgnunethello.la \
- $(top_builddir)/src/util/libgnunetutil.la  
+ $(top_builddir)/src/util/libgnunetutil.la
 gnunet_hello_LDFLAGS = \
   $(GN_LIBINTL)
diff --git a/src/hello/hello-uri.c b/src/hello/hello-uri.c
new file mode 100644
index 000000000..bacaf697e
--- /dev/null
+++ b/src/hello/hello-uri.c
@@ -0,0 +1,891 @@
+/*
+     This file is part of GNUnet.
+     Copyright (C) 2022 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
+ */
+
+/**
+ * @file hello/hello-uri.c
+ * @brief helper library for handling URI-based HELLOs
+ * @author Christian Grothoff
+ *
+ * Note:
+ * - Current API does not support deserializing HELLO of
+ *   another peer and then serializing it into another
+ *   format (we always require the private key).
+ *   Not sure if we need this, but if we do, we need
+ *   to extend the builder and the API.
+ * - Current API does not allow overriding the default
+ *   HELLO expiration time. We may want to add a function
+ *   that does this to create bootstrap HELLOs shipped with
+ *   the TGZ.
+ */
+#include "platform.h"
+#include "gnunet_signatures.h"
+#include "gnunet_hello_uri_lib.h"
+#include "gnunet_protocols.h"
+#include "gnunet_util_lib.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed as part of a HELLO block/URL.
+ */
+struct HelloSignaturePurpose
+{
+  /**
+   * Purpose must be #GNUNET_SIGNATURE_PURPOSE_HELLO
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When does the signature expire?
+   */
+  struct GNUNET_TIME_AbsoluteNBO expiration_time;
+
+  /**
+   * Hash over all addresses.
+   */
+  struct GNUNET_HashCode h_addrs;
+
+};
+
+/**
+ * Message used when gossiping HELLOs between peers.
+ */
+struct HelloUriMessage
+{
+  /**
+   * Type must be #GNUNET_MESSAGE_TYPE_HELLO_URI
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * Reserved. 0.
+   */
+  uint16_t reserved GNUNET_PACKED;
+
+  /**
+   * Number of URLs encoded after the end of the struct, in NBO.
+   */
+  uint16_t url_counter GNUNET_PACKED;
+
+  /* followed by a 'block' */
+};
+
+
+/**
+ * Start of a 'block'.
+ */
+struct BlockHeader
+{
+  /**
+   * Public key of the peer.
+   */
+  struct GNUNET_PeerIdentity pid;
+
+  /**
+   * Signature over the block, of purpose #GNUNET_SIGNATURE_PURPOSE_HELLO.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+
+  /**
+   * When does the HELLO expire?
+   */
+  struct GNUNET_TIME_AbsoluteNBO expiration_time;
+
+};
+
+
+/**
+ * Message used when a DHT provides its HELLO to direct
+ * neighbours.
+ */
+struct DhtHelloMessage
+{
+  /**
+   * Type must be #GNUNET_MESSAGE_TYPE_DHT_P2P_HELLO
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * Reserved. 0.
+   */
+  uint16_t reserved GNUNET_PACKED;
+
+  /**
+   * Number of URLs encoded after the end of the struct, in NBO.
+   */
+  uint16_t url_counter GNUNET_PACKED;
+
+  /**
+   * Signature over the block, of purpose #GNUNET_SIGNATURE_PURPOSE_HELLO.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+
+  /**
+   * When does the HELLO expire?
+   */
+  struct GNUNET_TIME_AbsoluteNBO expiration_time;
+
+  /* followed by the serialized addresses of the 'block' */
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Address of a peer.
+ */
+struct Address
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct Address *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct Address *prev;
+
+  /**
+   * Actual URI, allocated at the end of this struct.
+   */
+  const char *uri;
+
+  /**
+   * Length of @a uri including 0-terminator.
+   */
+  size_t uri_len;
+};
+
+
+/**
+ * Context for building (or parsing) HELLO URIs.
+ */
+struct GNUNET_HELLO_Builder
+{
+  /**
+   * Public key of the peer.
+   */
+  struct GNUNET_PeerIdentity pid;
+
+  /**
+   * Head of the addresses DLL.
+   */
+  struct Address *a_head;
+
+  /**
+   * Tail of the addresses DLL.
+   */
+  struct Address *a_tail;
+
+  /**
+   * Length of the @a a_head DLL.
+   */
+  unsigned int a_length;
+
+};
+
+
+/**
+ * Compute @a hash over addresses in @a builder.
+ *
+ * @param builder the builder to hash addresses of
+ * @param[out] hash where to write the hash
+ */
+static void
+hash_addresses (const struct GNUNET_HELLO_Builder *builder,
+                struct GNUNET_HashCode *hash)
+{
+  struct GNUNET_HashContext *hc;
+
+  hc = GNUNET_CRYPTO_hash_context_start ();
+  for (struct Address *a = builder->a_head;
+       NULL != a;
+       a = a->next)
+  {
+    GNUNET_CRYPTO_hash_context_read (hc,
+                                     a->uri,
+                                     a->uri_len);
+  }
+  GNUNET_CRYPTO_hash_context_finish (hc,
+                                     hash);
+
+}
+
+
+/**
+ * Create HELLO signature.
+ *
+ * @param builder the builder to use
+ * @param et expiration time to sign
+ * @param priv key to sign with
+ * @param[out] sig where to write the signature
+ */
+static void
+sign_hello (const struct GNUNET_HELLO_Builder *builder,
+            struct GNUNET_TIME_Timestamp et,
+            const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
+            struct GNUNET_CRYPTO_EddsaSignature *sig)
+{
+  struct HelloSignaturePurpose hsp = {
+    .purpose.size = htonl (sizeof (hsp)),
+    .purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_HELLO),
+    .expiration_time = GNUNET_TIME_absolute_hton (et.abs_time)
+  };
+
+  hash_addresses (builder,
+                  &hsp.h_addrs);
+  GNUNET_CRYPTO_eddsa_sign (priv,
+                            &hsp,
+                            sig);
+}
+
+
+/**
+ * Verify HELLO signature.
+ *
+ * @param builder the builder to use
+ * @param et expiration time to verify
+ * @param sig signature to verify
+ * @return #GNUNET_OK if everything is ok, #GNUNET_NO if the
+ *    HELLO expired, #GNUNET_SYSERR if the signature is wrong
+ */
+static enum GNUNET_GenericReturnValue
+verify_hello (const struct GNUNET_HELLO_Builder *builder,
+              struct GNUNET_TIME_Absolute et,
+              const struct GNUNET_CRYPTO_EddsaSignature *sig)
+{
+  struct HelloSignaturePurpose hsp = {
+    .purpose.size = htonl (sizeof (hsp)),
+    .purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_HELLO),
+    .expiration_time = GNUNET_TIME_absolute_hton (et)
+  };
+
+  hash_addresses (builder,
+                  &hsp.h_addrs);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (GNUNET_SIGNATURE_PURPOSE_HELLO,
+                                  &hsp,
+                                  sig,
+                                  &builder->pid.public_key))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_TIME_absolute_is_past (et))
+    return GNUNET_NO;
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_new (const struct GNUNET_PeerIdentity *pid)
+{
+  struct GNUNET_HELLO_Builder *builder;
+
+  builder = GNUNET_new (struct GNUNET_HELLO_Builder);
+  builder->pid = *pid;
+  return builder;
+}
+
+
+void
+GNUNET_HELLO_builder_free (struct GNUNET_HELLO_Builder *builder)
+{
+  struct Address *a;
+
+  while (NULL != (a = builder->a_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (builder->a_head,
+                                 builder->a_tail,
+                                 a);
+    builder->a_length--;
+    GNUNET_free (a);
+  }
+  GNUNET_assert (0 == builder->a_length);
+  GNUNET_free (builder);
+}
+
+
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_from_msg (const struct GNUNET_MessageHeader *msg)
+{
+  const struct HelloUriMessage *h;
+  uint16_t size = ntohs (msg->size);
+
+  if (GNUNET_MESSAGE_TYPE_HELLO_URI != ntohs (msg->type))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  if (sizeof (struct HelloUriMessage) > size)
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  h = (const struct HelloUriMessage *) msg;
+  size -= sizeof (*h);
+  return GNUNET_HELLO_builder_from_block (&h[1],
+                                          size);
+}
+
+
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_from_block (const void *block,
+                                 size_t block_size)
+{
+  const struct BlockHeader *bh = block;
+  struct GNUNET_HELLO_Builder *b;
+
+  if (block_size < sizeof (*bh))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  b = GNUNET_HELLO_builder_new (&bh->pid);
+  block += sizeof (*bh);
+  block_size -= sizeof (*bh);
+  while (block_size > 0)
+  {
+    const void *end = memchr (block,
+                              '\0',
+                              block_size);
+
+    if (NULL == end)
+    {
+      GNUNET_break_op (0);
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+    if (GNUNET_OK !=
+        GNUNET_HELLO_builder_add_address (b,
+                                          block))
+    {
+      GNUNET_break_op (0);
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+    end++;
+    block_size -= (end - block);
+    block = end;
+  }
+  {
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = verify_hello (b,
+                        GNUNET_TIME_absolute_ntoh (bh->expiration_time),
+                        &bh->sig);
+    GNUNET_break (GNUNET_SYSERR != ret);
+    if (GNUNET_OK != ret)
+    {
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+  }
+  return b;
+}
+
+
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_from_url (const char *url)
+{
+  const char *q;
+  const char *s1;
+  const char *s2;
+  struct GNUNET_PeerIdentity pid;
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+  struct GNUNET_TIME_Absolute et;
+  size_t len;
+  struct GNUNET_HELLO_Builder *b;
+
+  if (0 != strncasecmp (url,
+                        "gnunet://hello/",
+                        strlen ("gnunet://hello/")))
+    return NULL;
+  url += strlen ("gnunet://hello/");
+  s1 = strchr (url, '/');
+  if (NULL == s1)
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  s2 = strchr (s1 + 1, '/');
+  if (NULL == s1)
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  q = strchr (url, '?');
+  if (NULL == q)
+    q = url + strlen (url);
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (url,
+                                     s1 - url,
+                                     &pid,
+                                     sizeof(pid)))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (s1 + 1,
+                                     s2 - (s1 + 1),
+                                     &sig,
+                                     sizeof(sig)))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  {
+    unsigned long long sec;
+    char dummy = '?';
+
+    if ( (0 == sscanf (s2 + 1,
+                       "%llu%c",
+                       &sec,
+                       &dummy)) ||
+         ('?' != dummy) )
+    {
+      GNUNET_break_op (0);
+      return NULL;
+    }
+    et = GNUNET_TIME_absolute_from_s (sec);
+  }
+
+  b = GNUNET_HELLO_builder_new (&pid);
+  len = strlen (q);
+  while (len > 0)
+  {
+    const char *eq;
+    const char *amp;
+    char *addr = NULL;
+    char *uri;
+
+    /* skip ?/& separator */
+    len--;
+    q++;
+    eq = strchr (q, '=');
+    if ( (eq == q) ||
+         (NULL == eq) )
+    {
+      GNUNET_break_op (0);
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+    amp = strchr (eq, '&');
+    if (NULL == amp)
+      amp = &q[len];
+    GNUNET_STRINGS_urldecode (eq + 1,
+                              amp - (eq + 1),
+                              &addr);
+    if ( (NULL == addr) ||
+         (0 == strlen (addr)) )
+    {
+      GNUNET_free (addr);
+      GNUNET_break_op (0);
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+    GNUNET_asprintf (&uri,
+                     "%.*s://%s",
+                     (int) (eq - q),
+                     q,
+                     addr);
+    GNUNET_free (addr);
+    if (GNUNET_OK !=
+        GNUNET_HELLO_builder_add_address (b,
+                                          uri))
+    {
+      GNUNET_break_op (0);
+      GNUNET_free (uri);
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+    GNUNET_free (uri);
+    /* move to next URL */
+    len -= (amp - q);
+    q = amp;
+  }
+
+  {
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = verify_hello (b,
+                        et,
+                        &sig);
+    GNUNET_break (GNUNET_SYSERR != ret);
+    if (GNUNET_OK != ret)
+    {
+      GNUNET_HELLO_builder_free (b);
+      return NULL;
+    }
+  }
+  return b;
+}
+
+
+struct GNUNET_MQ_Envelope *
+GNUNET_HELLO_builder_to_env (const struct GNUNET_HELLO_Builder *builder,
+                             const struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
+{
+  struct GNUNET_MQ_Envelope *env;
+  struct HelloUriMessage *msg;
+  size_t blen;
+
+  if (builder->a_length > UINT16_MAX)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  blen = 0;
+  GNUNET_assert (GNUNET_NO ==
+                 GNUNET_HELLO_builder_to_block (builder,
+                                                priv,
+                                                NULL,
+                                                &blen));
+  env = GNUNET_MQ_msg_extra (msg,
+                             blen,
+                             GNUNET_MESSAGE_TYPE_HELLO_URI);
+  msg->url_counter = htonl ((uint16_t) builder->a_length);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_HELLO_builder_to_block (builder,
+                                                priv,
+                                                &msg[1],
+                                                &blen));
+  return env;
+}
+
+
+struct GNUNET_MessageHeader *
+GNUNET_HELLO_builder_to_dht_hello_msg (
+  const struct GNUNET_HELLO_Builder *builder,
+  const struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
+{
+  struct DhtHelloMessage *msg;
+  size_t blen;
+
+  if (builder->a_length > UINT16_MAX)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  blen = 0;
+  GNUNET_assert (GNUNET_NO ==
+                 GNUNET_HELLO_builder_to_block (builder,
+                                                priv,
+                                                NULL,
+                                                &blen));
+  GNUNET_assert (blen < UINT16_MAX);
+  GNUNET_assert (blen >= sizeof (struct BlockHeader));
+  {
+    char buf[blen] GNUNET_ALIGN;
+    const struct BlockHeader *block = (const struct BlockHeader *) buf;
+
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_HELLO_builder_to_block (builder,
+                                                  priv,
+                                                  buf,
+                                                  &blen));
+    msg = GNUNET_malloc (sizeof (*msg)
+                         + blen
+                         - sizeof (*block));
+    msg->header.type = htons (GNUNET_MESSAGE_TYPE_DHT_P2P_HELLO);
+    msg->header.size = htons (sizeof (*msg)
+                              + blen
+                              - sizeof (*block));
+    memcpy (&msg[1],
+            &block[1],
+            blen - sizeof (*block));
+    msg->sig = block->sig;
+    msg->expiration_time = block->expiration_time;
+  }
+  msg->url_counter = htonl ((uint16_t) builder->a_length);
+  return &msg->header;
+}
+
+
+char *
+GNUNET_HELLO_builder_to_url (const struct GNUNET_HELLO_Builder *builder,
+                             const struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
+{
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+  struct GNUNET_TIME_Timestamp et;
+  char *result;
+  char *pids;
+  char *sigs;
+  const char *sep = "?";
+
+  et = GNUNET_TIME_relative_to_timestamp (GNUNET_HELLO_ADDRESS_EXPIRATION);
+  sign_hello (builder,
+              et,
+              priv,
+              &sig);
+  pids = GNUNET_STRINGS_data_to_string_alloc (&builder->pid,
+                                              sizeof (builder->pid));
+  sigs = GNUNET_STRINGS_data_to_string_alloc (&sig,
+                                              sizeof (sig));
+  GNUNET_asprintf (&result,
+                   "gnunet://hello/%s/%s/%llu",
+                   pids,
+                   sigs,
+                   (unsigned long long) GNUNET_TIME_timestamp_to_s (et));
+  GNUNET_free (sigs);
+  GNUNET_free (pids);
+  for (struct Address *a = builder->a_head;
+       NULL != a;
+       a = a->next)
+  {
+    char *ue;
+    char *tmp;
+    int pfx_len;
+    const char *eou;
+
+    eou = strstr (a->uri,
+                  "://");
+    if (NULL == eou)
+    {
+      GNUNET_break (0);
+      GNUNET_free (result);
+      return NULL;
+    }
+    pfx_len = eou - a->uri;
+    eou += 3;
+    GNUNET_STRINGS_urlencode (eou,
+                              a->uri_len - 4 - pfx_len,
+                              &ue);
+    GNUNET_asprintf (&tmp,
+                     "%s%s%.*s=%s",
+                     result,
+                     sep,
+                     pfx_len,
+                     a->uri,
+                     ue);
+    GNUNET_free (ue);
+    GNUNET_free (result);
+    result = tmp;
+    sep = "&";
+  }
+  return result;
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_builder_to_block (const struct GNUNET_HELLO_Builder *builder,
+                               const struct GNUNET_CRYPTO_EddsaPrivateKey 
*priv,
+                               void *block,
+                               size_t *block_size)
+{
+  struct BlockHeader bh;
+  size_t needed = sizeof (bh);
+  char *pos;
+  struct GNUNET_TIME_Timestamp et;
+
+  for (struct Address *a = builder->a_head;
+       NULL != a;
+       a = a->next)
+  {
+    GNUNET_assert (needed + a->uri_len > needed);
+    needed += a->uri_len;
+  }
+  if ( (NULL == block) ||
+       (needed < *block_size) )
+  {
+    *block_size = needed;
+    return GNUNET_NO;
+  }
+  bh.pid = builder->pid;
+  et = GNUNET_TIME_relative_to_timestamp (GNUNET_HELLO_ADDRESS_EXPIRATION);
+  bh.expiration_time = GNUNET_TIME_absolute_hton (et.abs_time);
+  sign_hello (builder,
+              et,
+              priv,
+              &bh.sig);
+  memcpy (block,
+          &bh,
+          sizeof (bh));
+  pos = block + sizeof (bh);
+  for (struct Address *a = builder->a_head;
+       NULL != a;
+       a = a->next)
+  {
+    memcpy (pos,
+            a->uri,
+            a->uri_len);
+    pos += a->uri_len;
+  }
+  *block_size = needed;
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_builder_add_address (struct GNUNET_HELLO_Builder *builder,
+                                  const char *address)
+{
+  size_t alen = strlen (address) + 1;
+  struct Address *a;
+  const char *e;
+
+  if (NULL == (e = strstr (address,
+                           "://")))
+  {
+    GNUNET_break_op (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid address `%s'\n",
+                address);
+    return GNUNET_SYSERR;
+  }
+  if (e == address)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  for (const char *p = address; p != e; p++)
+    if ( (! isalpha ((unsigned char) *p)) &&
+         ('+' != *p) )
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  /* check for duplicates */
+  for (a = builder->a_head;
+       NULL != a;
+       a = a->next)
+    if (0 == strcmp (address,
+                     a->uri))
+      return GNUNET_NO;
+  a = GNUNET_malloc (sizeof (struct Address) + alen);
+  a->uri_len = alen;
+  memcpy (&a[1],
+          address,
+          alen);
+  a->uri = (const char *) &a[1];
+  GNUNET_CONTAINER_DLL_insert_tail (builder->a_head,
+                                    builder->a_tail,
+                                    a);
+  builder->a_length++;
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_builder_del_address (struct GNUNET_HELLO_Builder *builder,
+                                  const char *address)
+{
+  struct Address *a;
+
+  /* check for duplicates */
+  for (a = builder->a_head;
+       NULL != a;
+       a = a->next)
+    if (0 == strcmp (address,
+                     a->uri))
+      break;
+  if (NULL == a)
+    return GNUNET_NO;
+  GNUNET_CONTAINER_DLL_remove (builder->a_head,
+                               builder->a_tail,
+                               a);
+  builder->a_length--;
+  GNUNET_free (a);
+  return GNUNET_OK;
+}
+
+
+void
+GNUNET_HELLO_builder_iterate (const struct GNUNET_HELLO_Builder *builder,
+                              struct GNUNET_PeerIdentity *pid,
+                              GNUNET_HELLO_UriCallback uc,
+                              void *uc_cls)
+{
+  struct Address *nxt;
+
+  *pid = builder->pid;
+  if (NULL == uc)
+    return;
+  for (struct Address *a = builder->a_head;
+       NULL != a;
+       a = nxt)
+  {
+    nxt = a->next;
+    uc (uc_cls,
+        a->uri);
+  }
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_dht_msg_to_block (const struct GNUNET_MessageHeader *hello,
+                               const struct GNUNET_PeerIdentity *pid,
+                               void **block,
+                               size_t *block_size,
+                               struct GNUNET_TIME_Absolute *block_expiration)
+{
+  const struct DhtHelloMessage *msg
+    = (const struct DhtHelloMessage *) hello;
+  uint16_t len = ntohs (hello->size);
+  struct BlockHeader *bh;
+  struct GNUNET_HELLO_Builder *b;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (GNUNET_MESSAGE_TYPE_DHT_P2P_HELLO != ntohs (hello->type))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (len < sizeof (*msg))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  len -= sizeof (*msg);
+  *block_size = len + sizeof (*bh);
+  *block = GNUNET_malloc (*block_size);
+  bh = *block;
+  bh->pid = *pid;
+  bh->sig = msg->sig;
+  bh->expiration_time = msg->expiration_time;
+  *block_expiration = GNUNET_TIME_absolute_ntoh (msg->expiration_time);
+  memcpy (&bh[1],
+          &msg[1],
+          len);
+  b = GNUNET_HELLO_builder_from_block (*block,
+                                       *block_size);
+  if (NULL == b)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (*block);
+    *block_size = 0;
+    return GNUNET_SYSERR;
+  }
+  ret = verify_hello (b,
+                      *block_expiration,
+                      &msg->sig);
+  GNUNET_HELLO_builder_free (b);
+  if (GNUNET_SYSERR == ret)
+  {
+    GNUNET_free (*block);
+    *block_size = 0;
+    return GNUNET_SYSERR;
+  }
+  return ret;
+}
diff --git a/src/hello/test_hello-ng.c b/src/hello/test_hello-ng.c
index e6b1d42a0..4ace9439f 100644
--- a/src/hello/test_hello-ng.c
+++ b/src/hello/test_hello-ng.c
@@ -1,3 +1,22 @@
+/*
+     This file is part of GNUnet.
+     Copyright (C) 2022 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
+ */
 #include "platform.h"
 #include "gnunet_util_lib.h"
 #include "gnunet_nt_lib.h"
@@ -23,12 +42,12 @@ main (int argc,
                              GNUNET_NT_LAN,
                              t,
                              &privKey,
-                             (void**)&res,
+                             (void**) &res,
                              &res_len);
   GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
               "%s\n", res);
   GNUNET_assert (NULL !=
-                 GNUNET_HELLO_extract_address ((void**)res,
+                 GNUNET_HELLO_extract_address ((void**) res,
                                                res_len,
                                                &pid,
                                                &nt,
diff --git a/src/hello/test_hello-uri.c b/src/hello/test_hello-uri.c
new file mode 100644
index 000000000..7e70d6763
--- /dev/null
+++ b/src/hello/test_hello-uri.c
@@ -0,0 +1,212 @@
+/*
+     This file is part of GNUnet.
+     Copyright (C) 2022 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
+ */
+/**
+ * @file hello/test_hello-uri.c
+ * @brief test for helper library for handling URI-based HELLOs
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "gnunet_signatures.h"
+#include "gnunet_hello_uri_lib.h"
+#include "gnunet_util_lib.h"
+
+
+/**
+ * Check for expected URIs.
+ *
+ * @param cls a `unsigned int*`, bitmask set to found URIs
+ * @param uri URI to check for
+ */
+static void
+check_uris (void *cls,
+            const char *uri)
+{
+  unsigned int *found = cls;
+
+  if (0 == strcmp (uri,
+                   "test://address"))
+    *found |= 1;
+  else if (0 == strcmp (uri,
+                        "test://more"))
+    *found |= 2;
+  else
+    *found = (unsigned int) -1;
+}
+
+
+int
+main (int argc,
+      char *argv[])
+{
+  struct GNUNET_PeerIdentity pid;
+  struct GNUNET_HELLO_Builder *b;
+  struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+
+  GNUNET_log_setup ("test-hell-uri",
+                    "WARNING",
+                    NULL);
+  GNUNET_CRYPTO_eddsa_key_create (&priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+                                      &pid.public_key);
+  b = GNUNET_HELLO_builder_new (&pid);
+  GNUNET_assert (GNUNET_SYSERR ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "invalid"));
+  GNUNET_assert (GNUNET_SYSERR ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "i%v://bla"));
+  GNUNET_assert (GNUNET_SYSERR ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "://empty"));
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "test://address"));
+  GNUNET_assert (GNUNET_NO ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "test://address"));
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "test://more"));
+  {
+    void *block;
+    size_t block_size = 0;
+    struct GNUNET_HELLO_Builder *b2;
+    struct GNUNET_PeerIdentity p2;
+    unsigned int found;
+
+    GNUNET_assert (GNUNET_NO ==
+                   GNUNET_HELLO_builder_to_block (b,
+                                                  &priv,
+                                                  NULL,
+                                                  &block_size));
+    GNUNET_assert (GNUNET_NO ==
+                   GNUNET_HELLO_builder_to_block (b,
+                                                  &priv,
+                                                  NULL,
+                                                  &block_size));
+    GNUNET_assert (0 != block_size);
+    block = GNUNET_malloc (block_size);
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_HELLO_builder_to_block (b,
+                                                  &priv,
+                                                  block,
+                                                  &block_size));
+    b2 = GNUNET_HELLO_builder_from_block (block,
+                                          block_size);
+    GNUNET_free (block);
+    GNUNET_assert (NULL != b2);
+    found = 0;
+    GNUNET_HELLO_builder_iterate (b2,
+                                  &p2,
+                                  &check_uris,
+                                  &found);
+    GNUNET_assert (3 == found);
+    GNUNET_assert (0 ==
+                   GNUNET_memcmp (&p2,
+                                  &pid));
+    GNUNET_HELLO_builder_free (b2);
+  }
+
+  {
+    char *url;
+    struct GNUNET_HELLO_Builder *b2;
+    struct GNUNET_PeerIdentity p2;
+    unsigned int found;
+
+    url = GNUNET_HELLO_builder_to_url (b,
+                                       &priv);
+    b2 = GNUNET_HELLO_builder_from_url (url);
+    GNUNET_free (url);
+    GNUNET_assert (NULL != b2);
+    found = 0;
+    GNUNET_HELLO_builder_iterate (b2,
+                                  &p2,
+                                  &check_uris,
+                                  &found);
+    GNUNET_assert (3 == found);
+    GNUNET_assert (0 ==
+                   GNUNET_memcmp (&p2,
+                                  &pid));
+    GNUNET_HELLO_builder_free (b2);
+  }
+
+  {
+    struct GNUNET_MQ_Envelope *env;
+    struct GNUNET_HELLO_Builder *b2;
+    struct GNUNET_PeerIdentity p2;
+    unsigned int found;
+
+    env = GNUNET_HELLO_builder_to_env (b,
+                                       &priv);
+    b2 = GNUNET_HELLO_builder_from_msg (GNUNET_MQ_env_get_msg (env));
+    GNUNET_free (env);
+    GNUNET_assert (NULL != b2);
+    found = 0;
+    GNUNET_HELLO_builder_iterate (b2,
+                                  &p2,
+                                  &check_uris,
+                                  &found);
+    GNUNET_assert (3 == found);
+    GNUNET_assert (0 ==
+                   GNUNET_memcmp (&p2,
+                                  &pid));
+    GNUNET_HELLO_builder_free (b2);
+  }
+
+  GNUNET_HELLO_builder_free (b);
+
+  GNUNET_CRYPTO_mpi_print_unsigned (priv.d,
+                                    sizeof (priv.d),
+                                    GCRYMPI_CONST_ONE);
+  priv.d[0] &= 248;
+  priv.d[31] &= 127;
+  priv.d[31] |= 64;
+  {
+    char *buf;
+
+    buf = GNUNET_STRINGS_data_to_string_alloc (&priv,
+                                               sizeof (priv));
+    fprintf (stderr,
+             "PK: %s\n",
+             buf);
+    GNUNET_free (buf);
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+                                      &pid.public_key);
+  b = GNUNET_HELLO_builder_new (&pid);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "a://first"));
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_HELLO_builder_add_address (b,
+                                                   "b://second"));
+  {
+    char *url;
+
+    url = GNUNET_HELLO_builder_to_url (b,
+                                       &priv);
+    fprintf (stderr,
+             "TV: %s\n",
+             url);
+    GNUNET_free (url);
+  }
+
+  return 0;
+}
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
index e4b02b8ee..8808f6802 100644
--- a/src/include/Makefile.am
+++ b/src/include/Makefile.am
@@ -57,6 +57,7 @@ gnunetinclude_HEADERS = \
   gnunet_gnsrecord_plugin.h \
   gnu_name_system_record_types.h \
   gnunet_hello_lib.h \
+  gnunet_hello_uri_lib.h \
   gnunet_helper_lib.h \
   gnunet_identity_service.h \
   gnunet_abe_lib.h \
diff --git a/src/include/gnunet_block_lib.h b/src/include/gnunet_block_lib.h
index 5640209a6..f6db8d642 100644
--- a/src/include/gnunet_block_lib.h
+++ b/src/include/gnunet_block_lib.h
@@ -118,6 +118,12 @@ enum GNUNET_BLOCK_Type
    */
   GNUNET_BLOCK_TYPE_REVOCATION = 12,
 
+  /**
+   * Type of a block that contains a DHT-NG HELLO for a peer (for
+   * DHT and CADET find-peer operations).
+   */
+  GNUNET_BLOCK_TYPE_DHT_URL_HELLO = 13,
+
   /**
    * Block to store a cadet regex state
    */
diff --git a/src/include/gnunet_datacache_lib.h 
b/src/include/gnunet_datacache_lib.h
index 11076e3e7..737a5c845 100644
--- a/src/include/gnunet_datacache_lib.h
+++ b/src/include/gnunet_datacache_lib.h
@@ -154,6 +154,7 @@ GNUNET_DATACACHE_get (struct GNUNET_DATACACHE_Handle *h,
  *
  * @param h handle to the datacache
  * @param key area of the keyspace to look into
+ * @param type entries of which type are relevant?
  * @param num_results number of results that should be returned to @a iter
  * @param iter maybe NULL (to just count)
  * @param iter_cls closure for @a iter
@@ -162,6 +163,7 @@ GNUNET_DATACACHE_get (struct GNUNET_DATACACHE_Handle *h,
 unsigned int
 GNUNET_DATACACHE_get_closest (struct GNUNET_DATACACHE_Handle *h,
                               const struct GNUNET_HashCode *key,
+                              enum GNUNET_BLOCK_Type type,
                               unsigned int num_results,
                               GNUNET_DATACACHE_Iterator iter,
                               void *iter_cls);
diff --git a/src/include/gnunet_datacache_plugin.h 
b/src/include/gnunet_datacache_plugin.h
index 24570be72..914aaf15c 100644
--- a/src/include/gnunet_datacache_plugin.h
+++ b/src/include/gnunet_datacache_plugin.h
@@ -165,6 +165,7 @@ struct GNUNET_DATACACHE_PluginFunctions
    *
    * @param cls closure (internal context for the plugin)
    * @param key area of the keyspace to look into
+   * @param type desired block type for the replies
    * @param num_results number of results that should be returned to @a iter
    * @param iter maybe NULL (to just count)
    * @param iter_cls closure for @a iter
@@ -173,6 +174,7 @@ struct GNUNET_DATACACHE_PluginFunctions
   unsigned int
   (*get_closest) (void *cls,
                   const struct GNUNET_HashCode *key,
+                  enum GNUNET_BLOCK_Type type,
                   unsigned int num_results,
                   GNUNET_DATACACHE_Iterator iter,
                   void *iter_cls);
diff --git a/src/include/gnunet_dht_service.h b/src/include/gnunet_dht_service.h
index 768b19fb1..e0f9f6fc3 100644
--- a/src/include/gnunet_dht_service.h
+++ b/src/include/gnunet_dht_service.h
@@ -101,14 +101,14 @@ enum GNUNET_DHT_RouteOption
   GNUNET_DHT_RO_RECORD_ROUTE = 2,
 
   /**
-   * This is a 'FIND-PEER' request, so approximate results are fine.
+   * Approximate results are fine.
    */
-  GNUNET_DHT_RO_FIND_PEER = 4,
+  GNUNET_DHT_RO_FIND_APPROXIMATE = 4,
 
   /**
-    * Flag given to monitors if this was the last hop for a GET/PUT.
-    * This is only used for internal processing.
-    */
+   * Flag given to monitors if this was the last hop for a GET/PUT.
+   * This is only used for internal processing.
+   */
   GNUNET_DHT_RO_LAST_HOP = 65535
 };
 
@@ -512,6 +512,63 @@ GNUNET_DHT_verify_path (const struct GNUNET_HashCode *key,
                         const struct GNUNET_PeerIdentity *me);
 
 
+/**
+ * Handle to get a HELLO URL from the DHT for manual bootstrapping.
+ */
+struct GNUNET_DHT_HelloGetHandle;
+
+
+/**
+ * Signature called with the result of a HELLO GET operation.
+ *
+ * @param cls closure
+ * @param hello_url the resulting HELLO URL, NULL on error
+ */
+typedef void
+(*GNUNET_DHT_HelloGetCallback)(void *cls,
+                               const char *hello_url);
+
+
+/**
+ * Obtain HELLO URL of the DHT identified by @a dht_handle.
+ *
+ * @param dht_handle DHT to query
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on failure
+ */
+struct GNUNET_DHT_HelloGetHandle *
+GNUNET_DHT_hello_get (struct GNUNET_DHT_Handle *dht_handle,
+                      GNUNET_DHT_HelloGetCallback cb,
+                      void *cb_cls);
+
+
+/**
+ * Cancel hello get operation.
+ *
+ * @param[in] hgh operation to cancel.
+ */
+void
+GNUNET_DHT_hello_get_cancel (struct GNUNET_DHT_HelloGetHandle *hgh);
+
+
+/**
+ * Offer HELLO URL of the DHT identified by @a dht_handle.
+ * Callback may be invoked once, only way to cancel is to
+ * disconnect @a dht_handle.
+ *
+ * @param dht_handle DHT to query
+ * @param url URL with a HELLO to offer to the DHT
+ * @param cb function called when done
+ * @param cb_cls closure for @a cb
+ */
+void
+GNUNET_DHT_hello_offer (struct GNUNET_DHT_Handle *dht_handle,
+                        const char *url,
+                        GNUNET_SCHEDULER_TaskCallback cb,
+                        void *cb_cls);
+
+
 #if 0                           /* keep Emacsens' auto-indent happy */
 {
 #endif
diff --git a/src/include/gnunet_dhtu_plugin.h b/src/include/gnunet_dhtu_plugin.h
index 2c97d5848..fa0b5f667 100644
--- a/src/include/gnunet_dhtu_plugin.h
+++ b/src/include/gnunet_dhtu_plugin.h
@@ -58,15 +58,6 @@ struct GNUNET_DHTU_Target;
 struct GNUNET_DHTU_PreferenceHandle;
 
 
-/**
- * Key used to identify peer's position in the DHT.
- */
-struct GNUNET_DHTU_HashKey
-{
-  struct GNUNET_HashCode sha512;
-};
-
-
 /**
  * The datastore service will pass a pointer to a struct
  * of this type as the first and only argument to the
@@ -88,7 +79,6 @@ struct GNUNET_DHTU_PluginEnvironment
    * Function to call with new addresses of this peer.
    *
    * @param cls the closure
-   * @param key hash position of this address in the DHT
    * @param address address under which we are likely reachable,
    *           pointer will remain valid until @e address_del_cb is called; to 
be used for HELLOs. Example: "ip+udp://1.1.1.1:2086/"
    * @param source handle for sending from this address, NULL if we can only 
receive
@@ -96,7 +86,6 @@ struct GNUNET_DHTU_PluginEnvironment
    */
   void
   (*address_add_cb)(void *cls,
-                    struct GNUNET_DHTU_HashKey *key,
                     const char *address,
                     struct GNUNET_DHTU_Source *source,
                     void **ctx);
@@ -128,20 +117,19 @@ struct GNUNET_DHTU_PluginEnvironment
    * that peer.
    *
    * @param cls the closure
-   * @param pk public key of the target,
-   *    pointer will remain valid until @e disconnect_cb is called
-   * @para peer_id hash position of the peer,
-   *    pointer will remain valid until @e disconnect_cb is called
    * @param target handle to the target,
    *    pointer will remain valid until @e disconnect_cb is called
+   * @para pid peer identity,
+   *    pointer will remain valid until @e disconnect_cb is called
    * @param[out] ctx storage space for DHT to use in association with this 
target
    */
   void
   (*connect_cb)(void *cls,
                 struct GNUNET_DHTU_Target *target,
-                struct GNUNET_DHTU_HashKey *key,
+                const struct GNUNET_PeerIdentity *pid,
                 void **ctx);
 
+
   /**
    * Function to call when we disconnected from a peer and can henceforth
    * cannot transmit to that peer anymore.
@@ -185,10 +173,12 @@ struct GNUNET_DHTU_PluginFunctions
    * Request creation of a session with a peer at the given @a address.
    *
    * @param cls closure (internal context for the plugin)
-   * @param address target address to connect to
+   * @param pid target identity of the peer to connect to
+   * @param address target address URI to connect to
    */
   void
   (*try_connect) (void *cls,
+                  const struct GNUNET_PeerIdentity *pid,
                   const char *address);
 
 
@@ -205,7 +195,7 @@ struct GNUNET_DHTU_PluginFunctions
           struct GNUNET_DHTU_Target *target);
 
   /**
-   * Do no long request underlay to keep the connection alive.
+   * Do no longer request underlay to keep the connection alive.
    *
    * @param cls closure
    * @param target connection to keep alive
diff --git a/src/include/gnunet_hello_uri_lib.h 
b/src/include/gnunet_hello_uri_lib.h
new file mode 100644
index 000000000..c109a151a
--- /dev/null
+++ b/src/include/gnunet_hello_uri_lib.h
@@ -0,0 +1,248 @@
+/*
+     This file is part of GNUnet.
+     Copyright (C) 2022 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 Christian Grothoff
+ * @file
+ * Helper library for handling HELLO URIs
+ *
+ * @defgroup hello_uri  Hello_Uri library
+ * Helper library for handling HELLO URIs
+ *
+ * @{
+ */
+
+#ifndef GNUNET_HELLO_URI_LIB_H
+#define GNUNET_HELLO_URI_LIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+#include "gnunet_util_lib.h"
+
+
+/**
+ * Context for building (or parsing) HELLO URIs.
+ */
+struct GNUNET_HELLO_Builder;
+
+
+/**
+ * For how long are HELLO signatures valid?
+ */
+#define GNUNET_HELLO_ADDRESS_EXPIRATION GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_DAYS, 2)
+
+
+/**
+ * Allocate builder.
+ *
+ * @param pid peer the builder is for
+ * @return new builder
+ */
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_new (const struct GNUNET_PeerIdentity *pid);
+
+
+/**
+ * Release resources of a @a builder.
+ *
+ * @param[in] builder to free
+ */
+void
+GNUNET_HELLO_builder_free (struct GNUNET_HELLO_Builder *builder);
+
+
+/**
+ * Parse @a msg into builder.
+ *
+ * @param msg message to parse
+ * @return builder, NULL on failure
+ */
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_from_msg (const struct GNUNET_MessageHeader *msg);
+
+
+/**
+ * Parse @a block into builder.
+ *
+ * @param block DHT block to parse
+ * @param block_size number of bytes in @a block
+ * @return builder, NULL on failure
+ */
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_from_block (const void *block,
+                                 size_t block_size);
+
+
+/**
+ * Parse GNUnet HELLO @a url into builder.
+ *
+ * @param url URL to parse
+ * @return builder, NULL on failure
+ */
+struct GNUNET_HELLO_Builder *
+GNUNET_HELLO_builder_from_url (const char *url);
+
+
+/**
+ * Generate envelope with GNUnet HELLO message (including
+ * peer ID) from a @a builder
+ *
+ * @param builder builder to serialize
+ * @param priv private key to use to sign the result
+ * @return HELLO message matching @a builder
+ */
+struct GNUNET_MQ_Envelope *
+GNUNET_HELLO_builder_to_env (const struct GNUNET_HELLO_Builder *builder,
+                             const struct GNUNET_CRYPTO_EddsaPrivateKey *priv);
+
+
+/**
+ * Generate DHT HELLO message (without peer ID) from a @a builder
+ *
+ * @param builder builder to serialize
+ * @param priv private key to use to sign the result
+ * @return HELLO message matching @a builder
+ */
+struct GNUNET_MessageHeader *
+GNUNET_HELLO_builder_to_dht_hello_msg (
+  const struct GNUNET_HELLO_Builder *builder,
+  const struct GNUNET_CRYPTO_EddsaPrivateKey *priv);
+
+
+/**
+ * Generate GNUnet HELLO URI from a @a builder
+ *
+ * @param builder builder to serialize
+ * @param priv private key to use to sign the result
+ * @return hello URI
+ */
+char *
+GNUNET_HELLO_builder_to_url (const struct GNUNET_HELLO_Builder *builder,
+                             const struct GNUNET_CRYPTO_EddsaPrivateKey *priv);
+
+/**
+ * Generate DHT block from a @a builder
+ *
+ * @param builder the builder to serialize
+ * @param priv private key to use to sign the result
+ * @param[out] block where to write the block, NULL to only calculate @a 
block_size
+ * @param[in,out] block_size input is number of bytes available in @a block,
+ *                           output is number of bytes needed in @a block
+ * @return #GNUNET_OK on success, #GNUNET_NO if @a block_size was too small
+ *      or if @a block was NULL
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_builder_to_block (const struct GNUNET_HELLO_Builder *builder,
+                               const struct GNUNET_CRYPTO_EddsaPrivateKey 
*priv,
+                               void *block,
+                               size_t *block_size);
+
+
+/**
+ * Add individual @a address to the @a builder
+ *
+ * @param[in,out] builder to update
+ * @param address address URI to add
+ * @return #GNUNET_OK on success, #GNUNET_NO if @a address was already
+ *     in @a builder
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_builder_add_address (struct GNUNET_HELLO_Builder *builder,
+                                  const char *address);
+
+
+/**
+ * Remove individual @a address from the @a builder
+ *
+ * @param[in,out] builder to update
+ * @param address address URI to remove
+ * @return #GNUNET_OK on success, #GNUNET_NO if @a address was not
+ *     in @a builder
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_builder_del_address (struct GNUNET_HELLO_Builder *builder,
+                                  const char *address);
+
+
+/**
+ * Callback function used to extract URIs from a builder.
+ *
+ * @param cls closure
+ * @param uri one of the URIs
+ */
+typedef void
+(*GNUNET_HELLO_UriCallback) (void *cls,
+                             const char *uri);
+
+
+/**
+ * Iterate over URIs in a builder.
+ *
+ * @param builder builder to iterate over
+ * @param[out] pid set to the peer the @a builder is for
+ * @param uc callback invoked for each URI, can be NULL
+ * @param uc_cls closure for @a addrgen
+ */
+void
+GNUNET_HELLO_builder_iterate (const struct GNUNET_HELLO_Builder *builder,
+                              struct GNUNET_PeerIdentity *pid,
+                              GNUNET_HELLO_UriCallback uc,
+                              void *uc_cls);
+
+
+/**
+ * Convert a DHT @a hello message to a HELLO @a block.
+ *
+ * @param hello the HELLO message
+ * @param pid peer that created the @a hello
+ * @param[out] block set to the HELLO block
+ * @param[out] block_size set to number of bytes in @a block
+ * @param[out] block_expiration set to expiration time of @a block
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if the @a hello is expired (@a block is set!)
+ *         #GNUNET_SYSERR if @a hello is invalid (@a block will be set to NULL)
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_HELLO_dht_msg_to_block (const struct GNUNET_MessageHeader *hello,
+                               const struct GNUNET_PeerIdentity *pid,
+                               void **block,
+                               size_t *block_size,
+                               struct GNUNET_TIME_Absolute *block_expiration);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_HELLO_URI_LIB_H */
+#endif
+
+/** @} */ /* end of group */
+
+/* end of gnunet_hello_uri_lib.h */
diff --git a/src/include/gnunet_protocols.h b/src/include/gnunet_protocols.h
index 9e278eb92..f2892e125 100644
--- a/src/include/gnunet_protocols.h
+++ b/src/include/gnunet_protocols.h
@@ -153,10 +153,10 @@ extern "C" {
  
******************************************************************************/
 
 /**
- * Previously used for HELLO messages used for communicating peer addresses.
+ * Latest HELLO messages used for communicating peer addresses.
  * Managed by libgnunethello.
  */
-#define GNUNET_MESSAGE_TYPE_HELLO_LEGACY 16
+#define GNUNET_MESSAGE_TYPE_HELLO_URI 16
 
 /**
  * HELLO message with friend only flag used for communicating peer addresses.
@@ -660,13 +660,26 @@ extern "C" {
 #define GNUNET_MESSAGE_TYPE_DHT_CLIENT_GET_RESULTS_KNOWN 156
 
 /**
- * DHT wants to use CORE to transmit data.
+ * HELLO advertising a neighbours addresses.
  */
-#define GNUNET_MESSAGE_TYPE_DHT_CORE 143
+#define GNUNET_MESSAGE_TYPE_DHT_P2P_HELLO 157
 
 /**
- * Further X-VINE DHT messages continued from 880
+ * Encapsulation of DHT messages in CORE service.
  */
+#define GNUNET_MESSAGE_TYPE_DHT_CORE 158
+
+/**
+ * HELLO URL send between client and service (in
+ * either direction).
+ */
+#define GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_URL 159
+
+/**
+ * Client requests DHT service's HELLO URL.
+ */
+#define GNUNET_MESSAGE_TYPE_DHT_CLIENT_HELLO_GET 161
+
 
 
/*******************************************************************************
  * HOSTLIST message types
@@ -1796,7 +1809,6 @@ extern "C" {
 #define GNUNET_MESSAGE_TYPE_SETU_P2P_SEND_FULL 710
 
 
-
 
/*******************************************************************************
  * SETI message types
  
******************************************************************************/
diff --git a/src/include/gnunet_strings_lib.h b/src/include/gnunet_strings_lib.h
index 09c547b09..bb8577b7a 100644
--- a/src/include/gnunet_strings_lib.h
+++ b/src/include/gnunet_strings_lib.h
@@ -458,7 +458,7 @@ GNUNET_STRINGS_base64url_decode (const char *data,
  *
  * @param data the data to encode
  * @param len the length of the input
- * @param output where to write the output (*output should be NULL,
+ * @param[out] out where to write the output (*output should be NULL,
  *   is allocated)
  * @return the size of the output
  */
diff --git a/src/include/gnunet_time_lib.h b/src/include/gnunet_time_lib.h
index b14439462..96413c3cc 100644
--- a/src/include/gnunet_time_lib.h
+++ b/src/include/gnunet_time_lib.h
@@ -762,10 +762,21 @@ GNUNET_TIME_absolute_from_s (uint64_t s_after_epoch);
  *
  * @param s_after_epoch seconds after epoch to convert
  * @return converted time value
- */struct GNUNET_TIME_Timestamp
+ */
+struct GNUNET_TIME_Timestamp
 GNUNET_TIME_timestamp_from_s (uint64_t s_after_epoch);
 
 
+/**
+ * Convert timestamp to number of seconds after the UNIX epoch.
+ *
+ * @param ts timestamp to convert
+ * @return converted time value
+ */
+uint64_t
+GNUNET_TIME_timestamp_to_s (struct GNUNET_TIME_Timestamp ts);
+
+
 /**
  * Convert absolute time from network byte order.
  *
diff --git a/src/transport/gnunet-service-transport.c 
b/src/transport/gnunet-service-transport.c
index 24cc6464a..fad2ca4a1 100644
--- a/src/transport/gnunet-service-transport.c
+++ b/src/transport/gnunet-service-transport.c
@@ -1628,8 +1628,8 @@ GST_receive_callback (void *cls,
   GST_neighbours_notify_data_recv (address, message);
   switch (type)
   {
-  case GNUNET_MESSAGE_TYPE_HELLO_LEGACY:
-    /* Legacy HELLO message, discard  */
+  case GNUNET_MESSAGE_TYPE_HELLO_URI:
+    /* Future HELLO message, discard  */
     return ret;
 
   case GNUNET_MESSAGE_TYPE_HELLO:
diff --git a/src/util/network.c b/src/util/network.c
index 688c37665..2f77bc54e 100644
--- a/src/util/network.c
+++ b/src/util/network.c
@@ -350,7 +350,8 @@ initialize_network_handle (struct GNUNET_NETWORK_Handle *h,
 
   if (h->fd >= FD_SETSIZE)
   {
-    GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (h));
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_NETWORK_socket_close (h));
     errno = EMFILE;
     return GNUNET_SYSERR;
   }
diff --git a/src/util/plugin.c b/src/util/plugin.c
index 39874a588..6ee41eec9 100644
--- a/src/util/plugin.c
+++ b/src/util/plugin.c
@@ -289,12 +289,12 @@ GNUNET_PLUGIN_unload (const char *library_name,
   done = resolve_function (pos,
                            "done");
   ret = NULL;
-  if (NULL != done)
-    ret = done (arg);
   if (NULL == prev)
     plugins = pos->next;
   else
     prev->next = pos->next;
+  if (NULL != done)
+    ret = done (arg);
   lt_dlclose (pos->handle);
   GNUNET_free (pos->name);
   GNUNET_free (pos);
diff --git a/src/util/strings.c b/src/util/strings.c
index a77f09022..7e218cc59 100644
--- a/src/util/strings.c
+++ b/src/util/strings.c
@@ -1813,12 +1813,19 @@ GNUNET_STRINGS_urldecode (const char *data,
   char *wpos = *out;
   size_t resl = 0;
 
-  while ('\0' != *rpos)
+  while ( ('\0' != *rpos) &&
+          (data + len != rpos) )
   {
     unsigned int num;
     switch (*rpos)
     {
     case '%':
+      if (rpos + 3 > data + len)
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (*out);
+        return 0;
+      }
       if (1 != sscanf (rpos + 1, "%2x", &num))
         break;
       *wpos = (char) ((unsigned char) num);
diff --git a/src/util/time.c b/src/util/time.c
index 83b39b4e8..68a6937a0 100644
--- a/src/util/time.c
+++ b/src/util/time.c
@@ -695,6 +695,13 @@ GNUNET_TIME_timestamp_from_s (uint64_t s_after_epoch)
 }
 
 
+uint64_t
+GNUNET_TIME_timestamp_to_s (struct GNUNET_TIME_Timestamp ts)
+{
+  return ts.abs_time.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+}
+
+
 struct GNUNET_TIME_Absolute
 GNUNET_TIME_absolute_ntoh (struct GNUNET_TIME_AbsoluteNBO a)
 {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]