diff --git a/.gitignore b/.gitignore
index 9d50e4d515febddf4cd80638f47681e418c55567..c79e0d153e35d6b063097a957b056815a9c8f10e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,11 @@ Makefile.in
 
 # Python
 *.pyc
+python/opendht.cpp
+python/setup.py
+
+# Doxygen
+doc/Doxyfile
 
 # git backup files
 *.orig
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2ea5b8347b1f2bfc8ef80b9c4b3b8a8ebb7e2dbf..4efc5af519e480a7622a1e80631786c7d141e16c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,6 +16,9 @@ option (OPENDHT_SYSTEMD "Install systemd module" OFF)
 option (OPENDHT_ARGON2 "Use included argon2 sources" OFF)
 option (OPENDHT_LTO "Build with LTO" OFF)
 option (OPENDHT_SANITIZE "Build with address sanitizer and stack protector" OFF)
+option (OPENDHT_PROXY_SERVER "Enable DHT proxy server, use Restbed and jsoncpp" OFF)
+option (OPENDHT_PROXY_SERVER_IDENTITY "Allow clients to use the node identity" OFF)
+option (OPENDHT_PROXY_CLIENT "Enable DHT proxy client, use Restbed and jsoncpp" OFF)
 
 find_package(Doxygen)
 option (OPENDHT_DOCUMENTATION "Create and install the HTML based API documentation (requires Doxygen)" ${DOXYGEN_FOUND})
@@ -41,6 +44,10 @@ if (NOT OPENDHT_ARGON2)
         set(OPENDHT_ARGON2 ON)
     endif()
 endif ()
+if (OPENDHT_PROXY_SERVER OR OPENDHT_PROXY_CLIENT)
+    find_package(Restbed REQUIRED)
+    find_package(Jsoncpp REQUIRED)
+endif()
 
 # Build flags
 set (CMAKE_CXX_STANDARD 11)
@@ -77,6 +84,9 @@ endif ()
 if (Nettle_INCLUDE_DIRS)
     include_directories (SYSTEM "${Nettle_INCLUDE_DIRS}")
 endif ()
+if (Jsoncpp_INCLUDE_DIRS)
+    include_directories (SYSTEM "${Jsoncpp_INCLUDE_DIRS}")
+endif ()
 link_directories (${Nettle_LIBRARY_DIRS})
 include_directories (
     ./
@@ -144,6 +154,25 @@ list (APPEND opendht_HEADERS
     include/opendht/indexation/pht.h
 )
 
+if (OPENDHT_PROXY_SERVER)
+  add_definitions(-DOPENDHT_PROXY_SERVER=true)
+  if (OPENDHT_PROXY_SERVER_IDENTITY)
+    add_definitions(-DOPENDHT_PROXY_SERVER_IDENTITY=true)
+  else ()
+    add_definitions(-DOPENDHT_PROXY_SERVER_IDENTITY=false)
+  endif()
+  list (APPEND opendht_HEADERS
+    include/opendht/dht_proxy_server.h
+  )
+  list (APPEND opendht_SOURCES
+    src/dht_proxy_server.cpp
+    src/base64.h
+    src/base64.cpp
+  )
+else ()
+    add_definitions(-DENABLE_PROXY_SERVER=false)
+endif ()
+
 if(OPENDHT_ARGON2)
     # make sure argon2 submodule is up to date and initialized
     message("Initializing Argon2 submodule")
@@ -177,7 +206,7 @@ if (OPENDHT_STATIC)
         target_link_libraries(opendht-static ${argon2_LIBRARIES})
         target_include_directories(opendht-static SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
     endif ()
-    target_link_libraries(opendht-static ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES})
+    target_link_libraries(opendht-static ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Restbed_LIBRARIES} ${Jsoncpp_LIBRARIES})
     install (TARGETS opendht-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
 endif ()
 
@@ -198,7 +227,7 @@ if (OPENDHT_SHARED)
         target_link_libraries(opendht PRIVATE ${argon2_LIBRARIES})
         target_include_directories(opendht SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
     endif ()
-    target_link_libraries(opendht PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES})
+    target_link_libraries(opendht PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Restbed_LIBRARIES} ${Jsoncpp_LIBRARIES})
     install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
 endif ()
 
diff --git a/README.md b/README.md
index 0b6d58bb3abd74f0679d6982d3d8fc32211bca77..1ce85436b4ef5d173dc7bb47d74e338f7c806600 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 <img src="https://raw.githubusercontent.com/savoirfairelinux/opendht/master/resources/opendht_logo_512.png" width="100" align="right">
 <br>
 <h1 style="margin-top:10px">
-    <a id="user-content-opendht-" class="anchor" href="/savoirfairelinux/opendht/blob/master/README.md#opendht-" aria-hidden="true"></a>OpenDHT 
+    <a id="user-content-opendht-" class="anchor" href="/savoirfairelinux/opendht/blob/master/README.md#opendht-" aria-hidden="true"></a>OpenDHT
 </h1>
 
 A lightweight C++11 Distributed Hash Table implementation originally based on https://github.com/jech/dht by Juliusz Chroboczek.
@@ -18,7 +18,7 @@ See the wiki: <https://github.com/savoirfairelinux/opendht/wiki>
 
 #### How-to build and install
 
-Build instructions : <https://github.com/savoirfairelinux/opendht/wiki/Build-the-library>
+Build instructions: <https://github.com/savoirfairelinux/opendht/wiki/Build-the-library>
 
 #### How-to build a simple client app
 ```bash
@@ -91,6 +91,8 @@ for r in results:
 - msgpack-c 1.2+, used for data serialization.
 - GnuTLS 3.3+, used for cryptographic operations.
 - Nettle 2.4+, a GnuTLS dependency for crypto.
+- (optional) restbed 4.0+, used for the REST API.
+- (optional) jsoncpp 1.7.4-3+, used for the REST API.
 - Build tested with GCC 4.8+ (GNU/Linux, Android, Windows with MinGW), Clang/LLVM (Linux, macOS).
 - Build tested with Microsoft Visual Studio 2015
 
diff --git a/cmake/FindJsoncpp.cmake b/cmake/FindJsoncpp.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..fccc2da33b82c31d32fd478aa6c06f96f6bb0e7b
--- /dev/null
+++ b/cmake/FindJsoncpp.cmake
@@ -0,0 +1,18 @@
+find_path (JSONCPP_INCLUDE jsoncpp
+           HINTS
+           "/usr/include"
+           "/usr/local/include"
+           "/opt/local/include"
+)
+
+if (JSONCPP_INCLUDE)
+    message(STATUS "${green}Found Jsoncpp: ${JSONCPP_INCLUDE}")
+else()
+    message(FATAL_ERROR "${red}Failed to locate Jsoncpp.}")
+endif()
+
+if (JSONCPP_INCLUDE)
+    set(JSONCPP_FOUND TRUE)
+    set(Jsoncpp_LIBRARIES jsoncpp)
+    set(Jsoncpp_INCLUDE_DIRS ${JSONCPP_INCLUDE}/jsoncpp)
+endif()
diff --git a/cmake/FindRestbed.cmake b/cmake/FindRestbed.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..05f1e814d5e14b5b9e977c6d610d648c03c8a3e8
--- /dev/null
+++ b/cmake/FindRestbed.cmake
@@ -0,0 +1,17 @@
+find_path (RESTBED_INCLUDE restbed
+           HINTS
+           "/usr/include"
+           "/usr/local/include"
+           "/opt/local/include"
+)
+
+if (RESTBED_INCLUDE)
+    message(STATUS "${green}Found Restbed: ${RESTBED_INCLUDE}")
+else()
+    message(FATAL_ERROR "${red}Failed to locate Restbed.}")
+endif()
+
+if (RESTBED_INCLUDE)
+    set(RESTBED_FOUND TRUE)
+    set(Restbed_LIBRARIES restbed)
+endif()
diff --git a/configure.ac b/configure.ac
index 504de170e45aa8b7d2f2b94e3a329110cbe80d88..5863244b04083e5af9886df5f3edab36dd7e7d77 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,26 @@ AM_COND_IF([ENABLE_TOOLS], [
   ])
 ])
 
+AC_ARG_ENABLE([proxy_server], AS_HELP_STRING([--enable-proxy-server], [Enable proxy server ability]), proxy_server=yes, proxy_server=no)
+AM_CONDITIONAL(ENABLE_PROXY_SERVER, test x$proxy_server == xyes)
+AM_COND_IF([ENABLE_PROXY_SERVER], [
+	AC_CHECK_LIB(restbed, exit,, AC_MSG_ERROR([Missing restbed files]))
+	PKG_CHECK_MODULES([Jsoncpp], [jsoncpp >= 1.7.4])
+	CPPFLAGS+=" -DOPENDHT_PROXY_SERVER=true -ljsoncpp -lrestbed"
+
+	AC_ARG_ENABLE([proxy_server_identity], AS_HELP_STRING([--enable-proxy-server-identity],
+		[Enable proxy server ability]), proxy_server_identity=yes, proxy_server_identity=no)
+	AM_CONDITIONAL(ENABLE_PROXY_SERVER_IDENTITY, test x$proxy_server_identity == xyes)
+	AM_COND_IF([ENABLE_PROXY_SERVER_IDENTITY], [
+		CPPFLAGS+=" -DOPENDHT_PROXY_SERVER_IDENTITY=true"
+	], [
+		CPPFLAGS+=" -DOPENDHT_PROXY_SERVER_IDENTITY=false"
+	])
+], [
+	CPPFLAGS+=" -DOPENDHT_PROXY_SERVER=false"
+])
+
+
 AC_CONFIG_FILES([doc/Doxyfile doc/Makefile])
 
 dnl Configure setup.py if we build the python module
diff --git a/include/opendht.h b/include/opendht.h
index 98d3ece943b652bf5624687a3951b9bff96b9373..e003a21631f8c4612412db8ed5d493037579f93e 100644
--- a/include/opendht.h
+++ b/include/opendht.h
@@ -19,6 +19,9 @@
 #pragma once
 
 #include "opendht/dhtrunner.h"
+#if OPENDHT_PROXY_SERVER
+#include "opendht/dht_proxy_server.h"
+#endif
 #include "opendht/log.h"
 #include "opendht/default_types.h"
 #include "opendht/indexation/pht.h"
diff --git a/include/opendht/callbacks.h b/include/opendht/callbacks.h
index 21f2dd0c0562d00e8306ffaccea394eca7c1acec..e354ba2184c9ca38f8fd2c887e8da7a6f9964e49 100644
--- a/include/opendht/callbacks.h
+++ b/include/opendht/callbacks.h
@@ -26,6 +26,10 @@
 #include <memory>
 #include <functional>
 
+#if OPENDHT_PROXY_SERVER
+#include <json/json.h>
+#endif //OPENDHT_PROXY_SERVER
+
 namespace dht {
 
 struct Node;
@@ -47,6 +51,12 @@ struct OPENDHT_PUBLIC NodeStats {
     unsigned table_depth;
     unsigned getKnownNodes() const { return good_nodes + dubious_nodes; }
     std::string toString() const;
+#if OPENDHT_PROXY_SERVER
+    /**
+     * Build a json object from a NodeStats
+     */
+    Json::Value toJson() const;
+#endif //OPENDHT_PROXY_SERVER
 };
 
 /**
diff --git a/include/opendht/dht_proxy_server.h b/include/opendht/dht_proxy_server.h
new file mode 100644
index 0000000000000000000000000000000000000000..d2eb6a3896072c03096c846bfd12380eb65c83f7
--- /dev/null
+++ b/include/opendht/dht_proxy_server.h
@@ -0,0 +1,168 @@
+/*
+ *  Copyright (C) 2017 Savoir-faire Linux Inc.
+ *  Author : Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or 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 <https://www.gnu.org/licenses/>.
+ */
+
+#if OPENDHT_PROXY_SERVER
+
+#pragma once
+
+#include "def.h"
+#include "sockaddr.h"
+#include "infohash.h"
+
+#include <thread>
+#include <memory>
+#include <restbed>
+
+namespace dht {
+
+class DhtRunner;
+
+/**
+ * Describes the REST API
+ */
+class OPENDHT_PUBLIC DhtProxyServer
+{
+public:
+    /**
+     * Start the Http server for OpenDHT
+     * @param dht the DhtRunner linked to this proxy server
+     * @param port to listen
+     * @note if the server fails to start (if port is already used or reserved),
+     * it will fails silently
+     */
+    DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port = 8000);
+    virtual ~DhtProxyServer();
+
+    DhtProxyServer(const DhtProxyServer& other) = delete;
+    DhtProxyServer(DhtProxyServer&& other) = delete;
+    DhtProxyServer& operator=(const DhtProxyServer& other) = delete;
+    DhtProxyServer& operator=(DhtProxyServer&& other) = delete;
+
+    /**
+     * Stop the DhtProxyServer
+     */
+    void stop();
+
+private:
+    /**
+     * Return the PublicKey id, the node id and node stats
+     * Method: GET "/"
+     * Result: HTTP 200, body: Node infos in JSON format
+     * On error: HTTP 503, body: {"err":"xxxx"}
+     * @param session
+     */
+    void getNodeInfo(const std::shared_ptr<restbed::Session>& session) const;
+
+    /**
+     * Return Values of a InfoHash
+     * Method: GET "/{InfoHash: .*}"
+     * Return: Multiple JSON object in parts. Example:
+     * Value in JSON format\n
+     * Value in JSON format
+     *
+     * On error: HTTP 503, body: {"err":"xxxx"}
+     * @param session
+     */
+    void get(const std::shared_ptr<restbed::Session>& session) const;
+
+    /**
+     * Listen incoming Values of a InfoHash.
+     * Method: LISTEN "/{InfoHash: .*}"
+     * Return: Multiple JSON object in parts. Example:
+     * Value in JSON format\n
+     * Value in JSON format
+     *
+     * On error: HTTP 503, body: {"err":"xxxx"}
+     * @param session
+     */
+    void listen(const std::shared_ptr<restbed::Session>& session) const;
+
+    /**
+     * Put a value on the DHT
+     * Method: POST "/{InfoHash: .*}"
+     * body = Value to put in JSON
+     * Return: HTTP 200 if success and the value put in JSON
+     * On error: HTTP 503, body: {"err":"xxxx"} if no dht
+     * HTTP 400, body: {"err":"xxxx"} if bad json or HTTP 502 if put fails
+     * @param session
+     */
+    void put(const std::shared_ptr<restbed::Session>& session) const;
+
+#if OPENDHT_PROXY_SERVER_IDENTITY
+    /**
+     * Put a value to sign by the proxy on the DHT
+     * Method: SIGN "/{InfoHash: .*}"
+     * body = Value to put in JSON
+     * Return: HTTP 200 if success and the value put in JSON
+     * On error: HTTP 503, body: {"err":"xxxx"} if no dht
+     * HTTP 400, body: {"err":"xxxx"} if bad json
+     * @param session
+     */
+    void putSigned(const std::shared_ptr<restbed::Session>& session) const;
+
+    /**
+     * Put a value to encrypt by the proxy on the DHT
+     * Method: ENCRYPT "/{hash: .*}"
+     * body = Value to put in JSON + "to":"infoHash"
+     * Return: HTTP 200 if success and the value put in JSON
+     * On error: HTTP 503, body: {"err":"xxxx"} if no dht
+     * HTTP 400, body: {"err":"xxxx"} if bad json
+     * @param session
+     */
+    void putEncrypted(const std::shared_ptr<restbed::Session>& session) const;
+#endif // OPENDHT_PROXY_SERVER_IDENTITY
+
+    /**
+     * Return Values of a InfoHash filtered by a value id
+     * Method: GET "/{InfoHash: .*}/{ValueId: .*}"
+     * Return: Multiple JSON object in parts. Example:
+     * Value in JSON format\n
+     * Value in JSON format
+     *
+     * On error: HTTP 503, body: {"err":"xxxx"}
+     * @param session
+     */
+    void getFiltered(const std::shared_ptr<restbed::Session>& session) const;
+
+    /**
+     * Respond allowed Methods
+     * Method: OPTIONS "/{hash: .*}"
+     * Return: HTTP 200 + Allow: allowed methods
+     * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
+     */
+    void handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const;
+
+    std::thread server_thread {};
+    std::unique_ptr<restbed::Service> service_;
+    std::shared_ptr<DhtRunner> dht_;
+
+    // Handle client quit for listen.
+    // NOTE: can be simplified when we will supports restbed 5.0
+    std::thread listenThread_;
+    struct SessionToHashToken {
+        std::shared_ptr<restbed::Session> session;
+        InfoHash hash;
+        std::future<size_t> token;
+    };
+    mutable std::vector<SessionToHashToken> currentListeners_;
+    std::atomic_bool stopListeners {false};
+};
+
+}
+
+#endif //OPENDHT_PROXY_SERVER
diff --git a/include/opendht/value.h b/include/opendht/value.h
index 6e5a33dbb62c307b29cb51f76262d7a7a4d79c9b..60dd1d99d8bca134e90291dde8548fe6f79625c9 100644
--- a/include/opendht/value.h
+++ b/include/opendht/value.h
@@ -37,6 +37,10 @@
 #include <chrono>
 #include <set>
 
+#if OPENDHT_PROXY_SERVER
+#include <json/json.h>
+#endif //OPENDHT_PROXY_SERVER
+
 namespace dht {
 
 struct Value;
@@ -346,6 +350,14 @@ struct OPENDHT_PUBLIC Value
     Value(ValueType::Id t, const uint8_t* dat_ptr, size_t dat_len, Id id = INVALID_ID)
      : id(id), type(t), data(dat_ptr, dat_ptr+dat_len) {}
 
+#if OPENDHT_PROXY_SERVER
+    /**
+     * Build a value from a json object
+     * @param json
+     */
+    Value(Json::Value& json);
+#endif //OPENDHT_PROXY_SERVER
+
     template <typename Type>
     Value(ValueType::Id t, const Type& d, Id id = INVALID_ID)
      : id(id), type(t), data(packMsg(d)) {}
@@ -417,6 +429,18 @@ struct OPENDHT_PUBLIC Value
         return ss.str();
     }
 
+#if OPENDHT_PROXY_SERVER
+    /**
+     * Build a json object from a value
+     * Example:
+     * {
+     *  "data":"base64ofdata",
+     *   id":"0", "seq":0,"type":3
+     * }
+     */
+    Json::Value toJson() const;
+#endif //OPENDHT_PROXY_SERVER
+
     /** Return the size in bytes used by this value in memory (minimum). */
     size_t size() const;
 
diff --git a/src/Makefile.am b/src/Makefile.am
index f4614795c8fc331ef881b806cc7ee69720a6c025..4ff14741cfdbfd909b1966869eb8cb623d09a111 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -54,6 +54,11 @@ nobase_include_HEADERS = \
         ../include/opendht/rng.h \
         ../include/opendht/indexation/pht.h
 
+if ENABLE_PROXY_SERVER
+libopendht_la_SOURCES += base64.h base64.cpp dht_proxy_server.cpp
+nobase_include_HEADERS += ../include/opendht/dht_proxy_server.h
+endif
+
 clean-local:
 	rm -rf libargon2.la
 
diff --git a/src/base64.cpp b/src/base64.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7e255582c8bbc60bbeb974bf5951324262be6757
--- /dev/null
+++ b/src/base64.cpp
@@ -0,0 +1,143 @@
+/*
+ *  Copyright (C) 2004-2017 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "base64.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/* Mainly based on the following stackoverflow question:
+ * http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c
+ */
+static const char encoding_table[] = {
+    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
+    'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+    'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+    'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
+    's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
+    '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+static const size_t mod_table[] = { 0, 2, 1 };
+
+char *base64_encode(const uint8_t *input, size_t input_length,
+                             char *output, size_t *output_length)
+{
+    size_t i, j;
+    size_t out_sz = *output_length;
+    *output_length = 4 * ((input_length + 2) / 3);
+    if (out_sz < *output_length || output == nullptr)
+        return nullptr;
+
+    for (i = 0, j = 0; i < input_length; ) {
+        uint8_t octet_a = i < input_length ? input[i++] : 0;
+        uint8_t octet_b = i < input_length ? input[i++] : 0;
+        uint8_t octet_c = i < input_length ? input[i++] : 0;
+
+        uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+        output[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
+        output[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
+        output[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
+        output[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
+    }
+
+    for (i = 0; i < mod_table[input_length % 3]; i++)
+        output[*output_length - 1 - i] = '=';
+
+    return output;
+}
+
+uint8_t *base64_decode(const char *input, size_t input_length,
+                             uint8_t *output, size_t *output_length)
+{
+    size_t i, j;
+    uint8_t decoding_table[256];
+
+    uint8_t c;
+    for (c = 0; c < 64; c++)
+        decoding_table[static_cast<int>(encoding_table[c])] = c;
+
+    if (input_length % 4 != 0 || input_length < 2)
+        return nullptr;
+
+    size_t out_sz = *output_length;
+    *output_length = input_length / 4 * 3;
+    if (input[input_length - 1] == '=')
+        (*output_length)--;
+    if (input[input_length - 2] == '=')
+        (*output_length)--;
+
+    if (out_sz < *output_length || output == nullptr)
+        return nullptr;
+
+    for (i = 0, j = 0; i < input_length;) {
+        uint8_t sextet_a = input[i] == '=' ? 0 & i++
+            : decoding_table[static_cast<int>(input[i++])];
+        uint8_t sextet_b = input[i] == '=' ? 0 & i++
+            : decoding_table[static_cast<int>(input[i++])];
+        uint8_t sextet_c = input[i] == '=' ? 0 & i++
+            : decoding_table[static_cast<int>(input[i++])];
+        uint8_t sextet_d = input[i] == '=' ? 0 & i++
+            : decoding_table[static_cast<int>(input[i++])];
+
+        uint32_t triple = (sextet_a << 3 * 6) +
+                          (sextet_b << 2 * 6) +
+                          (sextet_c << 1 * 6) +
+                          (sextet_d << 0 * 6);
+
+        if (j < *output_length)
+            output[j++] = (triple >> 2 * 8) & 0xFF;
+        if (j < *output_length)
+            output[j++] = (triple >> 1 * 8) & 0xFF;
+        if (j < *output_length)
+            output[j++] = (triple >> 0 * 8) & 0xFF;
+    }
+
+    return output;
+}
+
+std::string
+base64_encode(const std::vector<uint8_t>::const_iterator begin,
+              const std::vector<uint8_t>::const_iterator end)
+{
+    size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3);
+    std::string out;
+    out.resize(output_length);
+    base64_encode(&(*begin), std::distance(begin, end),
+                  &(*out.begin()), &output_length);
+    out.resize(output_length);
+    return out;
+}
+
+
+std::string
+base64_encode(const std::vector<unsigned char>& str)
+{
+    return base64_encode(str.cbegin(), str.cend());
+}
+
+std::string
+base64_decode(const std::string& str)
+{
+    size_t output_length = str.length() / 4 * 3 + 2;
+    std::vector<uint8_t> output;
+    output.resize(output_length);
+    base64_decode(str.data(), str.size(), output.data(), &output_length);
+    output.resize(output_length);
+    return std::string(output.begin(), output.end());
+}
diff --git a/src/base64.h b/src/base64.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f5f39e1ab55efdd9a07f006179882312a4f3f14
--- /dev/null
+++ b/src/base64.h
@@ -0,0 +1,36 @@
+/*
+ *  Copyright (C) 2004-2017 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+/**
+ * Encode a buffer in base64.
+ *
+ * @param str the input buffer
+ * @return a base64-encoded buffer
+ */
+std::string base64_encode(const std::vector<unsigned char>& str);
+/**
+ * Decode a buffer in base64.
+ *
+ * @param str the input buffer
+ * @return a base64-decoded buffer
+ */
+std::string base64_decode(const std::string& str);
diff --git a/src/callbacks.cpp b/src/callbacks.cpp
index 8908a2dff2405223e1c2715e6c2b2b1262cbbfaf..89719ef1113a98a593ae745b37646f52f401f75f 100644
--- a/src/callbacks.cpp
+++ b/src/callbacks.cpp
@@ -68,4 +68,24 @@ NodeStats::toString() const
     return ss.str();
 }
 
+#if OPENDHT_PROXY_SERVER
+/**
+ * Build a json object from a NodeStats
+ */
+Json::Value
+NodeStats::toJson() const
+{
+    Json::Value val;
+    val["good"] = static_cast<Json::LargestUInt>(good_nodes);
+    val["dubious"] = static_cast<Json::LargestUInt>(dubious_nodes);
+    val["incoming"] = static_cast<Json::LargestUInt>(incoming_nodes);
+    if (table_depth > 1) {
+        val["table_depth"] = static_cast<Json::LargestUInt>(table_depth);
+        unsigned long tot_nodes = 8 * std::exp2(table_depth);
+        val["network_size_estimation"] = static_cast<Json::LargestUInt>(tot_nodes);
+    }
+    return val;
+}
+#endif //OPENDHT_PROXY_SERVER
+
 }
diff --git a/src/dht_proxy_server.cpp b/src/dht_proxy_server.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..586a8b775cccfe169b21aab15d8867a164b2f89d
--- /dev/null
+++ b/src/dht_proxy_server.cpp
@@ -0,0 +1,392 @@
+/*
+ *  Copyright (C) 2017 Savoir-faire Linux Inc.
+ *  Author : Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or 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 <https://www.gnu.org/licenses/>.
+ */
+
+#if OPENDHT_PROXY_SERVER
+#include "dht_proxy_server.h"
+
+#include "default_types.h"
+#include "dhtrunner.h"
+#include "msgpack.hpp"
+
+#include <chrono>
+#include <functional>
+#include <json/json.h>
+#include <limits>
+
+using namespace std::placeholders;
+
+namespace dht {
+
+DhtProxyServer::DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port)
+: dht_(dht)
+{
+    // NOTE in c++14, use make_unique
+    service_ = std::unique_ptr<restbed::Service>(new restbed::Service());
+
+    server_thread = std::thread([this, port]() {
+        // Create endpoints
+        auto resource = std::make_shared<restbed::Resource>();
+        resource->set_path("/");
+        resource->set_method_handler("GET", std::bind(&DhtProxyServer::getNodeInfo, this, _1));
+        service_->publish(resource);
+        resource = std::make_shared<restbed::Resource>();
+        resource->set_path("/{hash: .*}");
+        resource->set_method_handler("GET", std::bind(&DhtProxyServer::get, this, _1));
+        resource->set_method_handler("LISTEN", std::bind(&DhtProxyServer::listen, this, _1));
+        resource->set_method_handler("POST", std::bind(&DhtProxyServer::put, this, _1));
+#if OPENDHT_PROXY_SERVER_IDENTITY
+        resource->set_method_handler("SIGN", std::bind(&DhtProxyServer::putSigned, this, _1));
+        resource->set_method_handler("ENCRYPT", std::bind(&DhtProxyServer::putEncrypted, this, _1));
+#endif // OPENDHT_PROXY_SERVER_IDENTITY
+        resource->set_method_handler("OPTIONS", std::bind(&DhtProxyServer::handleOptionsMethod, this, _1));
+        service_->publish(resource);
+        resource = std::make_shared<restbed::Resource>();
+        resource->set_path("/{hash: .*}/{value: .*}");
+        resource->set_method_handler("GET", std::bind(&DhtProxyServer::getFiltered, this, _1));
+        service_->publish(resource);
+
+        // Start server
+        auto settings = std::make_shared<restbed::Settings>();
+        settings->set_default_header("Content-Type", "application/json");
+        settings->set_default_header("Connection", "keep-alive");
+        std::chrono::milliseconds timeout(std::numeric_limits<int>::max());
+        settings->set_connection_timeout(timeout); // there is a timeout, but really huge
+        settings->set_port(port);
+        try {
+            service_->start(settings);
+        } catch(std::system_error& e) {
+            std::cerr << "Error running server on port " << port << ": " << e.what() << std::endl;
+        }
+    });
+
+    listenThread_ = std::thread([this]() {
+        auto stop = false;
+        while (!stop) {
+            auto listener = currentListeners_.begin();
+            while (listener != currentListeners_.end()) {
+                if (listener->session->is_closed() && dht_) {
+                    dht_->cancelListen(listener->hash, std::move(listener->token));
+                    // Remove listener if unused
+                    listener = currentListeners_.erase(listener);
+                } else {
+                     ++listener;
+                }
+            }
+            //NOTE: When supports restbed 5.0: service_->is_up() and remove stopListeners
+            stop = stopListeners;
+            if (!stop)
+                std::this_thread::sleep_for(std::chrono::seconds(1));
+        }
+    });
+}
+
+DhtProxyServer::~DhtProxyServer()
+{
+    stop();
+}
+
+void
+DhtProxyServer::stop()
+{
+    service_->stop();
+    stopListeners = true;
+    // listenThreads_ will stop because there is no more sessions
+    if (listenThread_.joinable())
+        listenThread_.join();
+    if (server_thread.joinable())
+        server_thread.join();
+}
+
+void
+DhtProxyServer::getNodeInfo(const std::shared_ptr<restbed::Session>& session) const
+{
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b*/)
+        {
+            if (dht_) {
+                Json::Value result;
+                auto id = dht_->getId();
+                if (id)
+                    result["id"] = id.toString();
+                result["node_id"] = dht_->getNodeId().toString();
+                result["ipv4"] = dht_->getNodesStats(AF_INET).toJson();
+                result["ipv6"] = dht_->getNodesStats(AF_INET6).toJson();
+                Json::FastWriter writer;
+                s->close(restbed::OK, writer.write(result));
+            }
+            else
+                s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+        }
+    );
+}
+
+void
+DhtProxyServer::get(const std::shared_ptr<restbed::Session>& session) const
+{
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    auto hash = request->get_path_parameter("hash");
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
+        {
+            if (dht_) {
+                InfoHash infoHash(hash);
+                if (!infoHash) {
+                    infoHash = InfoHash::get(hash);
+                }
+                s->yield(restbed::OK, "", [=]( const std::shared_ptr< restbed::Session > s) {
+                    dht_->get(infoHash, [s](std::shared_ptr<Value> value) {
+                        // Send values as soon as we get them
+                        Json::FastWriter writer;
+                        s->yield(writer.write(value->toJson()), [](const std::shared_ptr<restbed::Session> /*session*/){ });
+                        return true;
+                    }, [s](bool /*ok* */) {
+                        // Communication is finished
+                        s->close();
+                    });
+                });
+            } else {
+                s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+            }
+        }
+    );
+
+}
+
+void
+DhtProxyServer::listen(const std::shared_ptr<restbed::Session>& session) const
+{
+
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    auto hash = request->get_path_parameter("hash");
+    InfoHash infoHash(hash);
+    if (!infoHash)
+        infoHash = InfoHash::get(hash);
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
+        {
+            if (dht_) {
+                InfoHash infoHash(hash);
+                if (!infoHash) {
+                    infoHash = InfoHash::get(hash);
+                }
+                s->yield(restbed::OK);
+                    // Handle client deconnection
+                    // NOTE: for now, there is no handler, so we test the session in a thread
+                    // will be the case in restbed 5.0
+                SessionToHashToken listener;
+                listener.session = session;
+                listener.hash = infoHash;
+                listener.token = dht_->listen(infoHash, [s](std::shared_ptr<Value> value) {
+                    // Send values as soon as we get them
+                    if (!s->is_closed()) {
+                        Json::FastWriter writer;
+                        s->yield(writer.write(value->toJson()), [](const std::shared_ptr<restbed::Session> /*session*/){ });
+                    }
+                    return !s->is_closed();
+                });
+                currentListeners_.emplace_back(std::move(listener));
+            } else {
+                session->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+            }
+        }
+    );
+}
+
+void
+DhtProxyServer::put(const std::shared_ptr<restbed::Session>& session) const
+{
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    auto hash = request->get_path_parameter("hash");
+    InfoHash infoHash(hash);
+    if (!infoHash)
+        infoHash = InfoHash::get(hash);
+
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
+        {
+            if (dht_) {
+                if(b.empty()) {
+                    std::string response("{\"err\":\"Missing parameters\"}");
+                    s->close(restbed::BAD_REQUEST, response);
+                } else {
+                    restbed::Bytes buf(b);
+                    Json::Value root;
+                    Json::Reader reader;
+                    std::string strJson(buf.begin(), buf.end());
+                    bool parsingSuccessful = reader.parse(strJson.c_str(), root);
+                    if (parsingSuccessful) {
+                        // Build the Value from json
+                        auto value = std::make_shared<Value>(root);
+
+                        dht_->put(infoHash, value, [s, value](bool ok) {
+                            if (ok) {
+                                Json::FastWriter writer;
+                                s->close(restbed::OK, writer.write(value->toJson()));
+                            } else {
+                                s->close(restbed::BAD_GATEWAY, "{\"err\":\"put failed\"}");
+                            }
+                        });
+                    } else {
+                        s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
+                    }
+                }
+            } else {
+                s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+            }
+        }
+    );
+}
+
+#if OPENDHT_PROXY_SERVER_IDENTITY
+void
+DhtProxyServer::putSigned(const std::shared_ptr<restbed::Session>& session) const
+{
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    auto hash = request->get_path_parameter("hash");
+    InfoHash infoHash(hash);
+    if (!infoHash)
+        infoHash = InfoHash::get(hash);
+
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
+        {
+            if (dht_) {
+                if(b.empty()) {
+                    std::string response("{\"err\":\"Missing parameters\"}");
+                    s->close(restbed::BAD_REQUEST, response);
+                } else {
+                    restbed::Bytes buf(b);
+                    Json::Value root;
+                    Json::Reader reader;
+                    std::string strJson(buf.begin(), buf.end());
+                    bool parsingSuccessful = reader.parse(strJson.c_str(), root);
+                    if (parsingSuccessful) {
+                        auto value = std::make_shared<Value>(root);
+
+                        Json::FastWriter writer;
+                        dht_->putSigned(infoHash, value);
+                        s->close(restbed::OK, writer.write(value->toJson()));
+                    } else {
+                        s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
+                    }
+                }
+            } else {
+                s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+            }
+        }
+    );
+}
+
+void
+DhtProxyServer::putEncrypted(const std::shared_ptr<restbed::Session>& session) const
+{
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    auto hash = request->get_path_parameter("hash");
+    InfoHash key(hash);
+    if (!key)
+        key = InfoHash::get(hash);
+
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
+        {
+            if (dht_) {
+                if(b.empty()) {
+                    std::string response("{\"err\":\"Missing parameters\"}");
+                    s->close(restbed::BAD_REQUEST, response);
+                } else {
+                    restbed::Bytes buf(b);
+                    Json::Value root;
+                    Json::Reader reader;
+                    std::string strJson(buf.begin(), buf.end());
+                    bool parsingSuccessful = reader.parse(strJson.c_str(), root);
+                    InfoHash to(root["to"].asString());
+                    if (parsingSuccessful && toInfoHash) {
+                        auto value = std::make_shared<Value>(root);
+                        Json::FastWriter writer;
+                        dht_->putEncrypted(key, to, value);
+                        s->close(restbed::OK, writer.write(value->toJson()));
+                    } else {
+                        if(!parsingSuccessful)
+                            s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
+                        else
+                            s->close(restbed::BAD_REQUEST, "{\"err\":\"No destination found\"}");
+                    }
+                }
+            } else {
+                s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+            }
+        }
+    );
+}
+#endif // OPENDHT_PROXY_SERVER_IDENTITY
+
+void
+DhtProxyServer::handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const
+{
+#if OPENDHT_PROXY_SERVER_IDENTITY
+    const auto allowed = "OPTIONS, GET, POST, LISTEN, SIGN, ENCRYPT";
+#else
+    const auto allowed = "OPTIONS, GET, POST, LISTEN";
+#endif //OPENDHT_PROXY_SERVER_IDENTITY
+    session->close(restbed::OK, {{"Access-Control-Allow-Methods", allowed},
+                                 {"Access-Control-Allow-Headers", "content-type"},
+                                 {"Access-Control-Max-Age", "86400"}});
+}
+
+void
+DhtProxyServer::getFiltered(const std::shared_ptr<restbed::Session>& session) const
+{
+    const auto request = session->get_request();
+    int content_length = std::stoi(request->get_header("Content-Length", "0"));
+    auto hash = request->get_path_parameter("hash");
+    auto value = request->get_path_parameter("value");
+    session->fetch(content_length,
+        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
+        {
+            if (dht_) {
+                InfoHash infoHash(hash);
+                if (!infoHash) {
+                    infoHash = InfoHash::get(hash);
+                }
+                s->yield(restbed::OK, "", [=]( const std::shared_ptr< restbed::Session > s) {
+                    dht_->get(infoHash, [s](std::shared_ptr<Value> v) {
+                        // Send values as soon as we get them
+                        Json::FastWriter writer;
+                        s->yield(writer.write(v->toJson()), [](const std::shared_ptr<restbed::Session> /*session*/){ });
+                        return true;
+                    }, [s](bool /*ok* */) {
+                        // Communication is finished
+                        s->close();
+                    }, {}, value);
+                });
+            } else {
+                s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+            }
+        }
+    );
+}
+
+}
+#endif //OPENDHT_PROXY_SERVER
diff --git a/src/value.cpp b/src/value.cpp
index e3d3aece8679e061a56f831897298c40d348cc61..759c17fb6f3016af8e85ee5752939d802f410f60 100644
--- a/src/value.cpp
+++ b/src/value.cpp
@@ -22,6 +22,11 @@
 #include "default_types.h"
 #include "securedht.h" // print certificate ID
 
+#if OPENDHT_PROXY_SERVER
+#include "base64.h"
+#endif //OPENDHT_PROXY_SERVER
+
+
 namespace dht {
 
 const std::string Query::QUERY_PARSE_ERROR {"Error parsing query."};
@@ -167,6 +172,71 @@ Value::msgpack_unpack_body(const msgpack::object& o)
     }
 }
 
+#if OPENDHT_PROXY_SERVER
+Value::Value(Json::Value& json)
+{
+   try {
+       if (json.isMember("id"))
+           id = ValueType::Id(json["id"].asInt());
+   } catch (...) { }
+   if (json.isMember("cypher")) {
+       auto cypherStr = json["cypher"].asString();
+       cypherStr = base64_decode(cypherStr);
+       cypher = std::vector<unsigned char>(cypherStr.begin(), cypherStr.end());
+   }
+   if (json.isMember("sig")) {
+       auto sigStr = json["sig"].asString();
+       sigStr = base64_decode(sigStr);
+       signature = std::vector<unsigned char>(sigStr.begin(), sigStr.end());
+   }
+   if (json.isMember("seq"))
+       seq = json["seq"].asInt();
+   if (json.isMember("owner")) {
+       auto ownerStr = json["owner"].asString();
+       auto ownerBlob = std::vector<unsigned char>(ownerStr.begin(), ownerStr.end());
+       owner = std::make_shared<const crypto::PublicKey>(ownerBlob);
+   }
+   if (json.isMember("to")) {
+       auto toStr = json["to"].asString();
+       recipient = InfoHash(toStr);
+   }
+   if (json.isMember("type"))
+       type = json["type"].asInt();
+   if (json.isMember("data")){
+       auto dataStr = json["data"].asString();
+       dataStr = base64_decode(dataStr);
+       data = std::vector<unsigned char>(dataStr.begin(), dataStr.end());
+   }
+   if (json.isMember("utype"))
+       user_type = json["utype"].asString();
+}
+
+Json::Value
+Value::toJson() const
+{
+    Json::Value val;
+    val["id"] = std::to_string(id);
+    if (isEncrypted()) {
+        val["cypher"] = base64_encode(cypher);
+    } else {
+        if (isSigned())
+            val["sig"] = base64_encode(signature);
+        bool has_owner = owner && *owner;
+        if (has_owner) { // isSigned
+            val["seq"] = seq;
+            val["owner"] = owner->toString();
+            if (recipient != InfoHash())
+                val["to"] = recipient.toString();
+        }
+        val["type"] = type;
+        val["data"] = base64_encode(data);
+        if (not user_type.empty())
+            val["utype"] = user_type;
+    }
+    return val;
+}
+#endif //OPENDHT_PROXY_SERVER
+
 bool
 FieldValue::operator==(const FieldValue& vfd) const
 {
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index a55d3d207e122b6ee3114ec5271762f65ab953c0..efad9767dc544d1b2cf24a611ecd1ef519347b70 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -29,7 +29,7 @@ extern "C" {
 using namespace dht;
 
 void print_usage() {
-    std::cout << "Usage: dhtnode [-v [-l logfile]] [-i] [-d] [-n network_id] [-p local_port] [-b bootstrap_host[:port]]" << std::endl << std::endl;
+    std::cout << "Usage: dhtnode [-v [-l logfile]] [-i] [-d] [-n network_id] [-p local_port] [-b bootstrap_host[:port]] [--proxyserver local_port]" << std::endl << std::endl;
     std::cout << "dhtnode, a simple OpenDHT command line node runner." << std::endl;
     std::cout << "Report bugs to: http://opendht.net" << std::endl;
 }
@@ -55,16 +55,23 @@ void print_help() {
               << "  ll         Print basic information and stats about the current node." << std::endl
               << "  ls [key]   Print basic information about current search(es)." << std::endl
               << "  ld [key]   Print basic information about currenty stored values on this node (or key)." << std::endl
-              << "  lr         Print the full current routing table of this node" << std::endl;
+              << "  lr         Print the full current routing table of this node." << std::endl;
+
+#if OPENDHT_PROXY_SERVER
+    std::cout << std::endl << "Operations with the proxy:" << std::endl
+              << "  pst [port]            Start the proxy interface on port." << std::endl
+              << "  psp [port]            Stop the proxy interface on port." << std::endl;
+#endif //OPENDHT_PROXY_SERVER
 
     std::cout << std::endl << "Operations on the DHT:" << std::endl
-              << "  b <ip:port>             Ping potential node at given IP address/port." << std::endl
+              << "  b <ip:port>           Ping potential node at given IP address/port." << std::endl
               << "  g <key>               Get values at <key>." << std::endl
               << "  l <key>               Listen for value changes at <key>." << std::endl
               << "  p <key> <str>         Put string value at <key>." << std::endl
               << "  pp <key> <str>        Put string value at <key> (persistent version)." << std::endl
               << "  s <key> <str>         Put string value at <key>, signed with our generated private key." << std::endl
               << "  e <key> <dest> <str>  Put string value at <key>, encrypted for <dest> with its public key (if found)." << std::endl;
+
     std::cout << std::endl << "Indexation operations on the DHT:" << std::endl
               << "  il <name> <key> [exact match]   Lookup the index named <name> with the key <key>." << std::endl
               << "                                  Set [exact match] to 'false' for inexact match lookup." << std::endl
@@ -83,6 +90,12 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
 #endif
 
     std::map<std::string, indexation::Pht> indexes;
+#if OPENDHT_PROXY_SERVER
+    std::map<in_port_t, std::unique_ptr<DhtProxyServer>> proxies;
+    if (params.proxyserver != 0) {
+        proxies.emplace(params.proxyserver, new DhtProxyServer(dht, params.proxyserver));
+    }
+#endif //OPENDHT_PROXY_SERVER
 
     while (true)
     {
@@ -160,11 +173,29 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
             dht->setLogFilter(filter);
             continue;
         }
+#if OPENDHT_PROXY_SERVER
+        else if (op == "pst") {
+            iss >> idstr;
+            try {
+                unsigned int port = std::stoi(idstr);
+                proxies.emplace(port, new DhtProxyServer(dht, port));
+            } catch (...) { }
+            continue;
+        } else if (op == "psp") {
+            iss >> idstr;
+            try {
+                auto it = proxies.find(std::stoi(idstr));
+                if (it != proxies.end())
+                    proxies.erase(it);
+            } catch (...) { }
+            continue;
+        }
+#endif //OPENDHT_PROXY_SERVER
 
         if (op.empty())
             continue;
 
-        static const std::set<std::string> VALID_OPS {"g", "l", "cl", "il", "ii", "p", "pp", "cpp", "s", "e", "a"};
+        static const std::set<std::string> VALID_OPS {"g", "l", "cl", "il", "ii", "p", "pp", "cpp", "s", "e", "a",  "q"};
         if (VALID_OPS.find(op) == VALID_OPS.cend()) {
             std::cout << "Unknown command: " << op << std::endl;
             std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl;
diff --git a/tools/tools_common.h b/tools/tools_common.h
index 2e4d72b082ff796923d879cbc363c7fd80f07722..39974fad6b57be732028302a2c1b3d316caa742c 100644
--- a/tools/tools_common.h
+++ b/tools/tools_common.h
@@ -119,6 +119,7 @@ struct dht_params {
     bool daemonize {false};
     bool service {false};
     std::pair<std::string, std::string> bootstrap {};
+    in_port_t proxyserver {0};
 };
 
 static const constexpr struct option long_options[] = {
@@ -132,6 +133,7 @@ static const constexpr struct option long_options[] = {
    {"service",    no_argument      , nullptr, 's'},
    {"logfile",    required_argument, nullptr, 'l'},
    {"syslog",     no_argument      , nullptr, 'L'},
+   {"proxyserver",required_argument, nullptr, 'S'},
    {nullptr,      0                , nullptr,  0}
 };
 
@@ -149,6 +151,14 @@ parseArgs(int argc, char **argv) {
                     std::cout << "Invalid port: " << port_arg << std::endl;
             }
             break;
+        case 'S': {
+                int port_arg = atoi(optarg);
+                if (port_arg >= 0 && port_arg < 0x10000)
+                    params.proxyserver = port_arg;
+                else
+                    std::cout << "Invalid port: " << port_arg << std::endl;
+            }
+            break;
         case 'n':
             params.network = strtoul(optarg, nullptr, 0);
             break;