diff --git a/CMakeLists.txt b/CMakeLists.txt
index 409ef786e46e62b32648e7eb7287cdb7f1df0a13..1a12a95474c292b447960e40d63b8beb98b18eed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,9 @@ set (CMAKE_CXX_FLAGS "-std=c++11 -Wno-return-type -Wall -Wextra -Wnon-virtual-dt
 
 find_package (GnuTLS 3.1 REQUIRED)
 find_package (Msgpack 1.1 REQUIRED)
+if (OPENDHT_TOOLS)
+	find_package (Readline 6 REQUIRED)
+endif ()
 
 list (APPEND opendht_SOURCES
 	src/infohash.cpp
diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..6807812f4d3383149af590ee8f3971e4d3f3ee2d
--- /dev/null
+++ b/cmake/FindReadline.cmake
@@ -0,0 +1,48 @@
+# - Try to find readline, a library for easy editing of command lines.
+# Variables used by this module:
+#  READLINE_ROOT_DIR     - Readline root directory
+# Variables defined by this module:
+#  READLINE_FOUND        - system has Readline
+#  READLINE_INCLUDE_DIR  - the Readline include directory (cached)
+#  READLINE_INCLUDE_DIRS - the Readline include directories
+#                          (identical to READLINE_INCLUDE_DIR)
+#  READLINE_LIBRARY      - the Readline library (cached)
+#  READLINE_LIBRARIES    - the Readline library plus the libraries it 
+#                          depends on
+
+# Copyright (C) 2009
+# ASTRON (Netherlands Institute for Radio Astronomy)
+# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+#
+# This program is free software; you can redistribute it and/or modify
+# modify it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: FindReadline.cmake 15228 2010-03-16 09:27:26Z loose $
+
+if(NOT READLINE_FOUND)
+
+  find_path(READLINE_INCLUDE_DIR readline/readline.h
+    HINTS ${READLINE_ROOT_DIR} PATH_SUFFIXES include)
+  find_library(READLINE_LIBRARY readline
+    HINTS ${READLINE_ROOT_DIR} PATH_SUFFIXES lib)
+  find_library(NCURSES_LIBRARY ncurses)   # readline depends on libncurses
+  mark_as_advanced(READLINE_INCLUDE_DIR READLINE_LIBRARY NCURSES_LIBRARY)
+
+  include(FindPackageHandleStandardArgs)
+  find_package_handle_standard_args(Readline DEFAULT_MSG
+    READLINE_LIBRARY NCURSES_LIBRARY READLINE_INCLUDE_DIR)
+
+  set(READLINE_INCLUDE_DIRS ${READLINE_INCLUDE_DIR})
+  set(READLINE_LIBRARIES ${READLINE_LIBRARY} ${NCURSES_LIBRARY})
+
+endif(NOT READLINE_FOUND)
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index f299d2123f619b0fa23514c811740335adc23b28..2db4ef3670806eef2db931089ea3aace27f1faa3 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -3,9 +3,9 @@ add_executable (dhtnode dhtnode.cpp tools_common.h)
 add_executable (dhtscanner dhtscanner.cpp tools_common.h)
 add_executable (dhtchat dhtchat.cpp tools_common.h)
 
-target_link_libraries (dhtnode LINK_PUBLIC opendht gnutls)
-target_link_libraries (dhtscanner LINK_PUBLIC opendht gnutls)
-target_link_libraries (dhtchat LINK_PUBLIC opendht gnutls)
+target_link_libraries (dhtnode LINK_PUBLIC opendht gnutls readline)
+target_link_libraries (dhtscanner LINK_PUBLIC opendht gnutls readline)
+target_link_libraries (dhtchat LINK_PUBLIC opendht gnutls readline)
 
 if (NOT DEFINED CMAKE_INSTALL_BINDIR)
 	set(CMAKE_INSTALL_BINDIR bin)
diff --git a/tools/dhtchat.cpp b/tools/dhtchat.cpp
index 831f4b4686d49a405707c15b22829cbd55f11f0f..c610b546560d60440855874a4084c190c7b0e580 100644
--- a/tools/dhtchat.cpp
+++ b/tools/dhtchat.cpp
@@ -72,18 +72,20 @@ main(int argc, char **argv)
     InfoHash room;
     const InfoHash myid = dht.getId();
 
+    // using the GNU History API
+    using_history();
+
     while (true)
     {
-        std::cout << (connected ? ">> " : "> ");
-        std::string line;
-        std::getline(std::cin, line);
-        static constexpr dht::InfoHash INVALID_ID {};
-
-        if (std::cin.eof())
+        // using the GNU Readline API
+        std::string line = readLine(connected ? PROMPT : "> ");
+        if (!line.empty() && line[0] == '\0')
             break;
         if (line.empty())
             continue;
 
+        static constexpr dht::InfoHash INVALID_ID {};
+
         std::istringstream iss(line);
         std::string op, idstr;
         iss >> op;
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index f9fc37ee0132450e7621cdc4cbcf4305b1bb8fe1..3dae0d3732292b0b86313a125badf57b52419f50 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -114,16 +114,21 @@ main(int argc, char **argv)
         print_node_info(dht, params);
         std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl << std::endl;
 
+        // using the GNU History API
+        using_history();
+
         while (true)
         {
-            std::cout << ">> ";
-            std::string line;
-            std::getline(std::cin, line);
+            // using the GNU Readline API
+            std::string line = readLine();
+            if (!line.empty() && line[0] == '\0')
+                break;
+
             std::istringstream iss(line);
             std::string op, idstr, value;
             iss >> op >> idstr;
 
-            if (std::cin.eof() || op == "x" || op == "q" || op == "exit" || op == "quit") {
+            if (op == "x" || op == "q" || op == "exit" || op == "quit") {
                 break;
             } else if (op == "h" || op == "help") {
                 print_help();
diff --git a/tools/tools_common.h b/tools/tools_common.h
index 4c7af5e213652a64c3844ac8f5376809b790be56..c1aebb090a71f79744d42833fa1a656cf1c91d2a 100644
--- a/tools/tools_common.h
+++ b/tools/tools_common.h
@@ -39,6 +39,8 @@
 
 #include <opendht.h>
 #include <getopt.h>
+#include <readline/readline.h>
+#include <readline/history.h>
 
 /**
  * Terminal colors for logging
@@ -202,3 +204,15 @@ parseArgs(int argc, char **argv) {
     }
     return params;
 }
+
+static const constexpr char* PROMPT = ">> ";
+
+std::string
+readLine(const char* prefix = PROMPT)
+{
+    const char* line_read = readline(prefix);
+    if (line_read && *line_read)
+        add_history(line_read);
+
+    return line_read ? std::string(line_read) : std::string("\0", 1);
+}