diff --git a/include/opendht/callbacks.h b/include/opendht/callbacks.h
index 3a4007257a76908f2a6c1064420945f583b68f2f..12d9f02392f3323d842bcb80d270cfbb73d993ca 100644
--- a/include/opendht/callbacks.h
+++ b/include/opendht/callbacks.h
@@ -39,6 +39,16 @@ enum class NodeStatus {
     Connected     // 1+ good nodes
 };
 
+struct OPENDHT_PUBLIC NodeStats {
+    unsigned good_nodes,
+             dubious_nodes,
+             cached_nodes,
+             incoming_nodes;
+    unsigned table_depth;
+    unsigned getKnownNodes() const { return good_nodes + dubious_nodes; }
+    std::string toString() const;
+};
+
 /**
  * Dht configuration.
  */
diff --git a/include/opendht/dht.h b/include/opendht/dht.h
index 82e7136daa69ebccc33cb521ebf3e22089cc242c..00ec6b8340519039e582eaa9a5959de87331997d 100644
--- a/include/opendht/dht.h
+++ b/include/opendht/dht.h
@@ -270,8 +270,8 @@ public:
     std::vector<ValuesExport> exportValues() const;
     void importValues(const std::vector<ValuesExport>&);
 
-    unsigned getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return,
-            unsigned *incoming_return) const;
+    NodeStats getNodesStats(sa_family_t af) const;
+
     std::string getStorageLog() const;
     std::string getStorageLog(const InfoHash&) const;
 
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index 1a7eecfe2d9b6a02e95ffde6b8a15a1d73fc941d..86042c4058d390c98a642969adb9bcf522654ed6 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -276,7 +276,8 @@ public:
         return running;
     }
 
-    int getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const;
+    NodeStats getNodesStats(sa_family_t af) const;
+    unsigned getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const;
 
     std::vector<unsigned> getNodeMessageStats(bool in = false) const;
     std::string getStorageLog() const;
diff --git a/src/callbacks.cpp b/src/callbacks.cpp
index bcdaf6fb7d1577c8602797aa59d7ddd9cf532c06..8908a2dff2405223e1c2715e6c2b2b1262cbbfaf 100644
--- a/src/callbacks.cpp
+++ b/src/callbacks.cpp
@@ -55,4 +55,17 @@ bindDoneCbSimple(DoneCallbackSimpleRaw raw_cb, void* user_data) {
     };
 }
 
-}
\ No newline at end of file
+std::string
+NodeStats::toString() const
+{
+    std::stringstream ss;
+    ss << "Known nodes: " << good_nodes << " good, " << dubious_nodes << " dubious, " << incoming_nodes << " incoming." << std::endl;
+    if (table_depth > 1) {
+        ss << "Routing table depth: " << table_depth << std::endl;
+        unsigned long tot_nodes = 8 * std::exp2(table_depth);
+        ss << "Network size estimation: " << tot_nodes << " nodes" << std::endl;
+    }
+    return ss.str();
+}
+
+}
diff --git a/src/dht.cpp b/src/dht.cpp
index 72a29ffd3233a7340b0c27c28832033397b4c759..d357c115508eb0cca888b774c8d1de698a96fc64 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -728,12 +728,11 @@ Dht::setLoggers(LogMethod error, LogMethod warn, LogMethod debug)
 NodeStatus
 Dht::getStatus(sa_family_t af) const
 {
-    unsigned good = 0, dubious = 0, cached = 0, incoming = 0;
-    unsigned tot = getNodesStats(af, &good, &dubious, &cached, &incoming);
+    const auto& stats = getNodesStats(af);
     auto& ping = af == AF_INET ? pending_pings4 : pending_pings6;
-    if (good)
+    if (stats.good_nodes)
         return NodeStatus::Connected;
-    if (ping or tot)
+    if (ping or stats.getKnownNodes())
         return NodeStatus::Connecting;
     return NodeStatus::Disconnected;
 }
@@ -2479,32 +2478,26 @@ Dht::tokenMatch(const Blob& token, const sockaddr *sa) const
     return false;
 }
 
-unsigned
-Dht::getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const
+NodeStats
+Dht::getNodesStats(sa_family_t af) const
 {
+    NodeStats stats {};
     const auto& now = scheduler.time();
-    unsigned good = 0, dubious = 0, cached = 0, incoming = 0;
-    for (const auto& b : buckets(af)) {
+    const auto& bcks = buckets(af);
+    for (const auto& b : bcks) {
         for (auto& n : b.nodes) {
             if (n->isGood(now)) {
-                good++;
+                stats.good_nodes++;
                 if (n->time > n->reply_time)
-                    incoming++;
+                    stats.incoming_nodes++;
             } else if (not n->isExpired())
-                dubious++;
+                stats.dubious_nodes++;
         }
         if (b.cached)
-            cached++;
-    }
-    if (good_return)
-        *good_return = good;
-    if (dubious_return)
-        *dubious_return = dubious;
-    if (cached_return)
-        *cached_return = cached;
-    if (incoming_return)
-        *incoming_return = incoming;
-    return good + dubious;
+            stats.cached_nodes++;
+    }
+    stats.table_depth = bcks.depth(bcks.findBucket(myid));
+    return stats;
 }
 
 void
diff --git a/src/dhtrunner.cpp b/src/dhtrunner.cpp
index bd8a5b23a3c616c6164370cfaf5dc6797a1dac33..21f670a2c5525fb944cabfb97fc510fcbbea5013 100644
--- a/src/dhtrunner.cpp
+++ b/src/dhtrunner.cpp
@@ -233,11 +233,27 @@ DhtRunner::importValues(const std::vector<ValuesExport>& values) {
     dht_->importValues(values);
 }
 
-int
+unsigned
 DhtRunner::getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const
 {
     std::lock_guard<std::mutex> lck(dht_mtx);
-    return dht_->getNodesStats(af, good_return, dubious_return, cached_return, incoming_return);
+    const auto stats = dht_->getNodesStats(af);
+    if (good_return)
+        *good_return = stats.good_nodes;
+    if (dubious_return)
+        *dubious_return = stats.dubious_nodes;
+    if (cached_return)
+        *cached_return = stats.cached_nodes;
+    if (incoming_return)
+        *incoming_return = stats.incoming_nodes;
+    return stats.good_nodes + stats.dubious_nodes;
+}
+
+NodeStats
+DhtRunner::getNodesStats(sa_family_t af) const
+{
+    std::lock_guard<std::mutex> lck(dht_mtx);
+    return dht_->getNodesStats(af);
 }
 
 std::vector<unsigned>
diff --git a/src/network_engine.cpp b/src/network_engine.cpp
index ba157a168183db8561ae22dc7fe4216de4ca732a..761b32898df719e6bd539dc93d4fc9c4750e306e 100644
--- a/src/network_engine.cpp
+++ b/src/network_engine.cpp
@@ -594,7 +594,7 @@ NetworkEngine::process(std::unique_ptr<ParsedMessage>&& msg, const SockAddr& fro
 }
 
 void
-packToken(msgpack::packer<msgpack::sbuffer>& pk, Blob token)
+packToken(msgpack::packer<msgpack::sbuffer>& pk, const Blob& token)
 {
     pk.pack_bin(token.size());
     pk.pack_bin_body((char*)token.data(), token.size());
diff --git a/src/routing_table.cpp b/src/routing_table.cpp
index 576b3d14cfe4aa9df11190455ac169e1d593ffee..54b08897cdbe326c80d9c70fc35f6a529807a7b2 100644
--- a/src/routing_table.cpp
+++ b/src/routing_table.cpp
@@ -70,6 +70,8 @@ RoutingTable::middle(const RoutingTable::const_iterator& it) const
 unsigned
 RoutingTable::depth(const RoutingTable::const_iterator& it) const
 {
+    if (it == end())
+        return 0;
     int bit1 = it->first.lowbit();
     int bit2 = std::next(it) != end() ? std::next(it)->first.lowbit() : -1;
     return std::max(bit1, bit2)+1;
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index 4d61b12656527c573d57f0e509a8395a96229008..24955c0406625b92d4c8742d5575cbaf8e953b09 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -101,12 +101,10 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
             continue;
         } else if (op == "ll") {
             print_node_info(dht, params);
-            unsigned good4, dubious4, cached4, incoming4;
-            unsigned good6, dubious6, cached6, incoming6;
-            dht->getNodesStats(AF_INET, &good4, &dubious4, &cached4, &incoming4);
-            dht->getNodesStats(AF_INET6, &good6, &dubious6, &cached6, &incoming6);
-            std::cout << "IPv4 nodes : " << good4 << " good, " << dubious4 << " dubious, " << incoming4 << " incoming." << std::endl;
-            std::cout << "IPv6 nodes : " << good6 << " good, " << dubious6 << " dubious, " << incoming6 << " incoming." << std::endl;
+            std::cout << "IPv4 stats:" << std::endl;
+            std::cout << dht->getNodesStats(AF_INET).toString() << std::endl;
+            std::cout << "IPv6 stats:" << std::endl;
+            std::cout << dht->getNodesStats(AF_INET6).toString() << std::endl;
             continue;
         } else if (op == "lr") {
             std::cout << "IPv4 routing table:" << std::endl;