diff --git a/tools/Makefile.am b/tools/Makefile.am
index 0e8ec15a404f8a7b999e8fc5a17919562967698f..c7a175f2595f069c5fd8c609fd987d4389df19a7 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -14,7 +14,7 @@ dhtscanner_LDFLAGS = -lopendht -lreadline -L@top_builddir@/src/.libs @GnuTLS_LIB
 
 if ENABLE_C
 bin_PROGRAMS += dhtcnode
-dhtcnode_CFLAGS = -isystem @top_srcdir@/c -isystem @top_srcdir@/include
+dhtcnode_CFLAGS = -std=c11 -isystem @top_srcdir@/c -isystem @top_srcdir@/include
 dhtcnode_SOURCES = dhtcnode.c
-dhtcnode_LDFLAGS = -lopendht-c -L@top_builddir@/c/.libs
+dhtcnode_LDFLAGS = -lopendht-c -lreadline -L@top_builddir@/c/.libs
 endif
diff --git a/tools/dhtcnode.c b/tools/dhtcnode.c
index 21d9da86bc06bb03311c92cef58ac8bc131bf33d..bcd901621febc7f42c0469e9685643aaddb88022 100644
--- a/tools/dhtcnode.c
+++ b/tools/dhtcnode.c
@@ -1,14 +1,23 @@
 #include <opendht_c.h>
 
+#include <getopt.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <arpa/inet.h>
+
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
-#include <arpa/inet.h>
+#include <stdatomic.h>
 
 struct op_context {
     dht_runner* runner;
-    int d;
+    atomic_bool stop;
+};
+struct listen_context {
+    dht_runner* runner;
+    dht_op_token* token;
 };
 
 bool dht_value_callback(const dht_value* value, bool expired, void* user_data)
@@ -26,16 +35,30 @@ bool dht_get_callback(const dht_value* value, void* user_data)
     return true;
 }
 
-void dht_done_callback(bool ok, void* user_data)
+void dht_get_done_callback(bool ok, void* user_data)
 {
     dht_runner* runner = (dht_runner*)user_data;
-    printf("Done callback. %s\n", ok ? "Success !" : "Failure :-(");
+    printf("Get completed: %s\n", ok ? "success !" : "failure :-(");
 }
 
-void op_context_free(void* user_data)
+void dht_put_done_callback(bool ok, void* user_data)
 {
+    dht_runner* runner = (dht_runner*)user_data;
+    printf("Put completed: %s\n", ok ? "success !" : "failure :-(");
+}
+
+void dht_shutdown_callback(void* user_data)
+{
+    printf("Stopped.\n");
     struct op_context* ctx = (struct op_context*)user_data;
-    printf("op_context_free %d.\n", ctx->d);
+    atomic_store(&ctx->stop, true);
+}
+
+void listen_context_free(void* user_data)
+{
+    printf("listen_context_free.\n");
+    struct listen_context* ctx = (struct listen_context*)user_data;
+    dht_op_token_delete(ctx->token);
     free(ctx);
 }
 
@@ -60,70 +83,152 @@ char* print_addr(const struct sockaddr* addr) {
     return s;
 }
 
-int main()
+struct dht_params {
+    bool help;
+    bool version;
+    bool generate_identity;
+    bool service;
+    bool peer_discovery;
+    bool log;
+    const char* bootstrap;
+    unsigned network;
+    in_port_t port;
+};
+
+static const struct option long_options[] = {
+    {"help",                    no_argument      , NULL, 'h'},
+    {"port",                    required_argument, NULL, 'p'},
+    {"net",                     required_argument, NULL, 'n'},
+    {"bootstrap",               required_argument, NULL, 'b'},
+    {"identity",                no_argument      , NULL, 'i'},
+    {"verbose",                 no_argument      , NULL, 'v'},
+    {"service",                 no_argument      , NULL, 's'},
+    {"peer-discovery",          no_argument      , NULL, 'D'},
+    {"no-rate-limit",           no_argument      , NULL, 'U'},
+    {"persist",                 required_argument, NULL, 'f'},
+    {"logfile",                 required_argument, NULL, 'l'},
+    {"syslog",                  no_argument      , NULL, 'L'},
+    {NULL,                      0                , NULL,  0}
+};
+
+struct dht_params
+parse_args(int argc, char **argv) {
+    struct dht_params params;
+    int opt;
+    while ((opt = getopt_long(argc, argv, "hisvDp:n:b:f:l:", long_options, NULL)) != -1) {
+        switch (opt) {
+        case 'p': {
+                int port_arg = atoi(optarg);
+                if (port_arg >= 0 && port_arg < 0x10000)
+                    params.port = port_arg;
+            }
+            break;
+        case 'D':
+            params.peer_discovery = true;
+            break;
+        case 'n':
+            params.network = strtoul(optarg, NULL, 0);
+            break;
+        case 'b':
+            params.bootstrap = (optarg[0] == '=') ? optarg+1 : optarg;
+            break;
+        case 'h':
+            params.help = true;
+            break;
+        case 'v':
+            params.log = true;
+            break;
+        case 'i':
+            params.generate_identity = true;
+            break;
+        case 's':
+            params.service = true;
+            break;
+        default:
+            break;
+        }
+    }
+    return params;
+}
+
+int main(int argc, char **argv)
 {
-    dht_identity id = dht_identity_generate("testNode", NULL);
-    dht_infohash cert_id = dht_certificate_get_id(id.certificate);
-    printf("Cert ID: %s\n", dht_infohash_print(&cert_id));
-
-    dht_publickey* pk = dht_certificate_get_publickey(id.certificate);
-    dht_infohash pk_id = dht_publickey_get_id(pk);
-    printf("PK ID: %s\n", dht_infohash_print(&pk_id));
-    dht_publickey_delete(pk);
-
-    pk = dht_privatekey_get_publickey(id.privatekey);
-    pk_id = dht_publickey_get_id(pk);
-    printf("Key ID: %s\n", dht_infohash_print(&pk_id));
-    dht_publickey_delete(pk);
-    dht_identity_delete(&id);
-
-    dht_infohash h;
-    //dht_infohash_random(&h);
-    dht_infohash_get_from_string(&h, "bad_actors_test");
-    printf("hash: %s\n", dht_infohash_print(&h));
-
-    // Put data
-    const char* data_str = "yo, this is some data";
-    dht_value* val = dht_value_new_from_string(data_str);
-
-    struct op_context* ctx = malloc(sizeof(struct op_context));
-    ctx->runner = runner;
-    ctx->d = 42;
+    struct dht_params params = parse_args(argc, argv);
 
     dht_runner* runner = dht_runner_new();
     dht_runner_config dht_config;
 	dht_runner_config_default(&dht_config);
-	dht_config.peer_discovery = true; // Look for other peers on the network
-	dht_config.peer_publish = true; // Publish our own peer info
-    dht_runner_run_config(runner, 4040, &dht_config);
-
-
-    // Get data
-    //dht_runner_get(runner, &h, dht_get_callback, dht_done_callback, runner);
-
-    // Listen for data
-    dht_op_token* token = dht_runner_listen(runner, &h, dht_value_callback, op_context_free, ctx);
-    dht_runner_bootstrap(runner, "bootstrap.jami.net", NULL);
-    dht_runner_put(runner, &h, val, dht_done_callback, runner, true);
-    dht_runner_get(runner, &h, dht_get_callback, dht_done_callback, runner);
-    dht_value_unref(val);
-
-    //sleep(1);
-    //sleep(20);
-
-    struct sockaddr** addrs = dht_runner_get_public_address(runner);
-    for (struct sockaddr** addrIt = addrs; *addrIt; addrIt++) {
-        struct sockaddr* addr = *addrIt;
-        char* addr_str = print_addr(addr);
-        free(addr);
-        printf("Found public address: %s\n", addr_str);
-        free(addr_str);
+	dht_config.peer_discovery = params.peer_discovery; // Look for other peers on the network
+	dht_config.peer_publish = params.peer_discovery; // Publish our own peer info
+    dht_config.dht_config.node_config.network = params.network;
+    dht_config.log = params.log;
+    dht_runner_run_config(runner, params.port, &dht_config);
+
+    if (params.bootstrap) {
+        printf("Bootstrap using %s\n", params.bootstrap);
+        dht_runner_bootstrap(runner, params.bootstrap, NULL);
     }
-    free(addrs);
-
-    dht_runner_cancel_listen(runner, &h, token);
-    dht_op_token_delete(token);
 
+    char cmd[64];
+    char arg[64];
+    char value[256];
+    while (true) {
+        const char* line_read = readline("> ");
+        if (line_read && *line_read)
+            add_history(line_read);
+        if (!line_read)
+            break;
+        if (!strcmp(line_read, "\0"))
+            continue;
+
+        bzero(cmd, sizeof cmd);
+        bzero(arg, sizeof arg);
+        bzero(value, sizeof value);
+        sscanf(line_read, "%64s %64s %256s", cmd, arg, value);
+        //printf("%s -> %s\n", cmd, arg);
+
+        if (!strcmp(cmd, "la")) {
+            struct sockaddr** addrs = dht_runner_get_public_address(runner);
+            if (addrs) {
+                for (struct sockaddr** addrIt = addrs; *addrIt; addrIt++) {
+                    struct sockaddr* addr = *addrIt;
+                    char* addr_str = print_addr(addr);
+                    free(addr);
+                    printf("Found public address: %s\n", addr_str);
+                    free(addr_str);
+                }
+                free(addrs);
+            }
+            continue;
+        }
+
+        dht_infohash key;
+        dht_infohash_from_hex(&key, arg);
+        if (dht_infohash_is_zero(&key)) {
+            dht_infohash_get_from_string(&key, arg);
+            printf("Using h(%s) = %s\n", arg, dht_infohash_print(&key));
+        }
+        if (!strcmp(cmd, "g")) {
+            dht_runner_get(runner, &key, dht_get_callback, dht_get_done_callback, runner);
+        } else if (!strcmp(cmd, "l")) {
+            struct listen_context* ctx = malloc(sizeof(struct listen_context));
+            ctx->runner = runner;
+            ctx->token = dht_runner_listen(runner, &key, dht_value_callback, listen_context_free, ctx);
+        } else if (!strcmp(cmd, "p")) {
+            dht_value* val = dht_value_new_from_string(value);
+            dht_runner_put(runner, &key, val, dht_put_done_callback, runner, true);
+            dht_value_unref(val);
+        }
+    }
+    printf("Stopping..\n");
+
+    struct op_context ctx;
+    ctx.runner = runner;
+    atomic_init(&ctx.stop, false);
+    dht_runner_shutdown(runner, dht_shutdown_callback, &ctx);
+    while (!atomic_load(&ctx.stop)) {
+        usleep(250);
+    }
     dht_runner_delete(runner);
     return 0;
 }