diff --git a/include/opendht/dht.h b/include/opendht/dht.h
index 263ee1511d6c0565a8bf0d0592d38c7a1a255475..c7bf07b0595ede07c94d10fd28e6b4944eac3dab 100644
--- a/include/opendht/dht.h
+++ b/include/opendht/dht.h
@@ -188,6 +188,10 @@ public:
     void importValues(const std::vector<ValuesExport>&);
 
     int getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const;
+    std::string getStorageLog() const;
+    std::string getRoutingTablesLog(sa_family_t af) const;
+    std::string getSearchesLog(sa_family_t af) const;
+
     void dumpTables() const;
 
     /* This must be provided by the user. */
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index afc11a5114db91838cb6e640af68510fcf99a4ed..82b4eeb0919da32b0821624ede5445a96107c411 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -89,6 +89,12 @@ public:
         return dht->getId();
     }
 
+    InfoHash getRoutingId() const {
+        if (!dht)
+            return {};
+        return dht->getRoutingId();
+    }
+
     std::vector<Dht::NodeExport> exportNodes() const {
         std::unique_lock<std::mutex> lck(dht_mtx);
         if (!dht)
@@ -122,6 +128,28 @@ public:
         return running;
     }
 
+    int getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const
+    {
+        std::unique_lock<std::mutex> lck(dht_mtx);
+        return dht->getNodesStats(af, good_return, dubious_return, cached_return, incoming_return);
+    }
+
+    std::string getStorageLog() const
+    {
+        std::unique_lock<std::mutex> lck(dht_mtx);
+        return dht->getStorageLog();
+    }
+    std::string getRoutingTablesLog(sa_family_t af) const
+    {
+        std::unique_lock<std::mutex> lck(dht_mtx);
+        return dht->getRoutingTablesLog(af);
+    }
+    std::string getSearchesLog(sa_family_t af) const
+    {
+        std::unique_lock<std::mutex> lck(dht_mtx);
+        return dht->getSearchesLog(af);
+    }
+
     /**
      * If threaded is false, loop() must be called periodically.
      */
diff --git a/src/dht.cpp b/src/dht.cpp
index e0ab1bc7cde8cd297e6ae4246cdacc544bf0a0c5..d0cd1e5dfd23a947461f5b4e1a350a6fbee37b15 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -1663,6 +1663,38 @@ Dht::dumpTables() const
     DHT_DEBUG("%s", out.str().c_str());
 }
 
+std::string
+Dht::getStorageLog() const
+{
+    std::stringstream out;
+    for (const auto& st : store) {
+        out << "Storage " << st.id << " " << st.listeners.size() << " list., " << st.values.size() << " values:" << std::endl;
+        for (const auto& v : st.values)
+            out << "   " << *v.data << " (" << (now.tv_sec - v.time) << "s)" << std::endl;
+    }
+    return out.str();
+}
+
+
+std::string 
+Dht::getRoutingTablesLog(sa_family_t af) const
+{
+    auto& list = (af == AF_INET) ? buckets : buckets6;
+    std::stringstream out;
+    for (const auto& b : list)
+        dumpBucket(b, out);
+    return out.str();
+}
+
+std::string 
+Dht::getSearchesLog(sa_family_t af) const
+{
+    auto& list = (af == AF_INET) ? buckets : buckets6;
+    std::stringstream out;
+    for (const auto& sr : searches)
+        dumpSearch(sr, out);
+    return out.str();
+}
 
 Dht::Dht(int s, int s6, const InfoHash& id)
  : dht_socket(s), dht_socket6(s6), myid(id)
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index e87fd88eb7bcbc8d2d3897abdf4c67e74c7abc5a..21cb4ac0392e84c3a216e1e6430fb8b759198a89 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -86,13 +86,18 @@ void printLog(std::ostream& s, char const* m, va_list args) {
 int
 main(int argc, char **argv)
 {
-    if (argc < 2)
-        throw std::invalid_argument("Entrez un port");
-
     int i = 1;
-    int p = atoi(argv[i++]);
-    if (p <= 0 || p >= 0x10000)
-        throw std::invalid_argument("Port invalide: " + std::to_string(p));
+    int p = 0;
+    if (argc >= 2) {
+        int a = atoi(argv[i]);
+        if (a > 0 && a < 0x10000) {
+            p = a;
+            i++;
+        }
+    }
+    if (!p)
+        p = 4222;
+
     in_port_t port = p;
 
     std::vector<sockaddr_storage> bootstrap_nodes {};
@@ -128,28 +133,68 @@ main(int argc, char **argv)
     auto crt_tmp = dht::crypto::generateIdentity("DHT Node", ca_tmp);
 
     DhtRunner dht;
-    dht.run(port, crt_tmp, true, [](dht::Dht::Status ipv4, dht::Dht::Status ipv6) {
-        std::cout << (int)ipv4 << (int)ipv6 << std::endl;
+    dht.run(port, crt_tmp, true, [](dht::Dht::Status /* ipv4 */, dht::Dht::Status /* ipv6 */) {
     });
 
-    dht.setLoggers(
-        [](char const* m, va_list args){ std::cerr << red; printLog(std::cerr, m, args); std::cerr << def; },
-        [](char const* m, va_list args){ std::cout << yellow; printLog(std::cout, m, args); std::cout << def; },
-        [](char const* m, va_list args){ printLog(std::cout, m, args); }
-    );
-
     dht.bootstrap(bootstrap_nodes);
 
+    std::cout << "OpenDht node " << dht.getRoutingId() << " running on port " <<  port<<  std::endl;
+    std::cout << "Public key ID " << dht.getId() << std::endl;
+
     while (true)
     {
+        std::cout << ">> ";
         std::string line;
         std::getline(std::cin, line);
         std::istringstream iss(line);
         std::string op, idstr, value;
         iss >> op >> idstr;
 
-        if (op == "x" || op == "q" || op == "exit" || op == "quit") {
+        if (std::cin.eof() || op == "x" || op == "q" || op == "exit" || op == "quit") {
             break;
+        } else if (op == "h" || op == "help") {
+            std::cout << "OpenDht command line interface (CLI)" << std::endl;
+            std::cout << "Possible commands:" << std::endl;
+            std::cout << "  h, help    Print this help message." << std::endl;
+            std::cout << "  q, quit    Quit the program." << std::endl;
+
+            std::cout << std::endl << "Node information:" << std::endl;
+            std::cout << "  ll         Print basic information and stats about the current node." << std::endl;
+            std::cout << "  ls         Print basic information about current searches." << std::endl;
+            std::cout << "  ld         Print basic information about currenty stored values on this node." << std::endl;
+            std::cout << "  lr         Print the full current routing table of this node" << std::endl;
+
+            std::cout << std::endl << "Operations on the DHT:" << std::endl;
+            std::cout << "  g [key]               Get values at [key]." << std::endl;
+            std::cout << "  l [key]               Listen for value changes at [key]." << std::endl;
+            std::cout << "  p [key] [str]         Put string value at [key]." << std::endl;
+            std::cout << "  s [key] [str]         Put string value at [key], signed with our generated private key." << std::endl;
+            std::cout << "  e [key] [dest] [str]  Put string value at [key], encrypted for [dest] with its public key (if found)." << std::endl;
+            std::cout << std::endl;
+            continue;
+        } else if (op == "ll") {
+            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 << "OpenDht node " << dht.getRoutingId() << std::endl;
+            std::cout << "Public key ID " << dht.getId() << std::endl;
+            std::cout << "IPv4 nodes : " << good4 << " good, " << dubious4 << " dubious, " << incoming4 << " incoming." << std::endl;
+            std::cout << "IPv6 nodes : " << good6 << " good, " << dubious6 << " dubious, " << incoming6 << " incoming." << std::endl;
+            continue;
+        } else if (op == "lr") {
+            std::cout << "IPv4 routing table:" << std::endl;
+            std::cout << dht.getRoutingTablesLog(AF_INET) << std::endl;
+            std::cout << "IPv6 routing table:" << std::endl;
+            std::cout << dht.getRoutingTablesLog(AF_INET6) << std::endl;
+            continue;
+        } else if (op == "ld") {
+            std::cout << dht.getStorageLog() << std::endl;
+            continue;
+        } else if (op == "ls") {
+            std::cout << "Searches:" << std::endl;
+            std::cout << dht.getSearchesLog(AF_INET) << std::endl;
+            continue;
         }
 
         dht::InfoHash id {idstr};
@@ -211,10 +256,13 @@ main(int argc, char **argv)
             dht.put(id, dht::Value {dht::ServiceAnnouncement::TYPE.id, dht::ServiceAnnouncement(port)}, [](bool ok) {
                 std::cout << "Announce done !" << ok << std::endl;
             });
+        } else {
+            std::cout << "Unknown command: " << op << std::endl;
+            std::cout << " (type 'h' or 'help' for help)" << std::endl;
         }
     }
 
-    std::cout <<  "Stopping node..." << std::endl;
+    std::cout << std::endl <<  "Stopping node..." << std::endl;
     dht.join();
 
     gnutls_global_deinit();