diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9574a3304c33616a030dd1d65fd50620019a1b71..467ad7815ab0391c2f1ac227b5bf96f27fec5c86 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_
 if (NOT CMAKE_BUILD_TYPE)
     set(CMAKE_BUILD_TYPE Release)
 endif ()
+add_definitions(-DPACKAGE_VERSION="${opendht_VERSION}")
 if (OPENDHT_LOG)
     add_definitions(-DOPENDHT_LOG=true)
 else ()
diff --git a/include/opendht/network_engine.h b/include/opendht/network_engine.h
index a92f9ef8c24c4b4c1097b5fddc372f3eebe3ace8..9e623825d07e871207b0e952f27f33a267f695b1 100644
--- a/include/opendht/network_engine.h
+++ b/include/opendht/network_engine.h
@@ -27,6 +27,7 @@
 #include "utils.h"
 #include "rng.h"
 #include "rate_limiter.h"
+#include "log_enable.h"
 
 #include <vector>
 #include <string>
diff --git a/include/opendht/peer_discovery.h b/include/opendht/peer_discovery.h
index 0ee71f3899fbdbae1963aac2f6fc3627a981fe9a..2f2dd94b9bd50831ea6fb5080c107b4b530ac7b4 100644
--- a/include/opendht/peer_discovery.h
+++ b/include/opendht/peer_discovery.h
@@ -18,11 +18,10 @@
 
 #pragma once
 
+#include "def.h"
 #include "sockaddr.h"
 #include "infohash.h"
 
-#include <string.h>
-#include <stdio.h>
 #include <unistd.h>
 
 #include <thread>
diff --git a/include/opendht/scheduler.h b/include/opendht/scheduler.h
index 13c57b6aed37a46053089c976049cd5d3c145d84..b5b1056975fbadc6d475a6036fc246a3c87cd2f1 100644
--- a/include/opendht/scheduler.h
+++ b/include/opendht/scheduler.h
@@ -21,7 +21,6 @@
 #pragma once
 
 #include "utils.h"
-#include "log_enable.h"
 
 #include <functional>
 #include <map>
diff --git a/include/opendht/utils.h b/include/opendht/utils.h
index 57b2b5d05f6890cc90c5b65d991eda1540258a62..0c33160aaab9d66e25e32a30661313250cce18c9 100644
--- a/include/opendht/utils.h
+++ b/include/opendht/utils.h
@@ -40,6 +40,8 @@ namespace dht {
 using NetId = uint32_t;
 using want_t = int_fast8_t;
 
+OPENDHT_PUBLIC const char* version();
+
 // shortcut for std::shared_ptr
 template<class T>
 using Sp = std::shared_ptr<T>;
diff --git a/src/utils.cpp b/src/utils.cpp
index 144bc48bd86a8a30abe741a1507d515c794b5a26..b509cbf9bc5dd6a8e336df79f2169439bbba2edd 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -25,10 +25,18 @@
 #define IN_IS_ADDR_UNSPECIFIED(a) (((long int) (a)->s_addr) == 0x00000000)
 #endif /* IN_IS_ADDR_UNSPECIFIED */
 
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION "(unknown version)"
+#endif
+
 namespace dht {
 
 static constexpr std::array<uint8_t, 12> MAPPED_IPV4_PREFIX {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}};
 
+const char* version() {
+    return PACKAGE_VERSION;
+}
+
 std::pair<std::string, std::string>
 splitPort(const std::string& s) {
     if (s.empty())
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index 45e6ef41a467efeb56ad4371844711753c7f1fdf..3d1937b8322fd2f71dba5a2f336219a209005001 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -29,24 +29,33 @@ 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]] [--proxyserver local_port]" << std::endl << std::endl;
+void print_info() {
     std::cout << "dhtnode, a simple OpenDHT command line node runner." << std::endl;
     std::cout << "Report bugs to: https://opendht.net" << std::endl;
 }
 
+void print_version() {
+    std::cout << "OpenDHT version " << dht::version() << std::endl;
+    print_info();
+}
+
+void print_usage() {
+    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;
+    print_info();
+}
+
 void print_id_req() {
     std::cout << "An identity is required to perform this operation (run with -i)" << std::endl;
 }
 
 void print_node_info(const std::shared_ptr<DhtRunner>& dht, const dht_params& params) {
-    std::cout << "OpenDht node " << dht->getNodeId() << " running on port " <<  dht->getBoundPort() << std::endl;
+    std::cout << "OpenDHT node " << dht->getNodeId() << " running on port " <<  dht->getBoundPort() << std::endl;
     if (params.generate_identity)
         std::cout << "Public key ID " << dht->getId() << std::endl;
 }
 
 void print_help() {
-    std::cout << "OpenDht command line interface (CLI)" << std::endl;
+    std::cout << "OpenDHT command line interface (CLI)" << std::endl;
     std::cout << "Possible commands:" << std::endl
               << "  h, help    Print this help message." << std::endl
               << "  x, quit    Quit the program." << std::endl
@@ -484,6 +493,10 @@ main(int argc, char **argv)
             print_usage();
             return 0;
         }
+        if (params.version) {
+            print_version();
+            return 0;
+        }
 
         if (params.daemonize) {
             daemonize();
diff --git a/tools/tools_common.h b/tools/tools_common.h
index f0ff4c26edbb4dc3c9b003f1b39761166cf548e5..ef41a0ed14f9317bc01b2fd0445e21350b4c81b7 100644
--- a/tools/tools_common.h
+++ b/tools/tools_common.h
@@ -90,6 +90,7 @@ static const constexpr in_port_t DHT_DEFAULT_PORT = 4222;
 
 struct dht_params {
     bool help {false}; // print help and exit
+    bool version {false};
     bool log {false};
     std::string logfile {};
     bool syslog {false};
@@ -124,6 +125,7 @@ static const constexpr struct option long_options[] = {
    {"proxyclient",      required_argument, nullptr, 'C'},
    {"pushserver",       required_argument, nullptr, 'y'},
    {"devicekey",        required_argument, nullptr, 'z'},
+   {"version",          no_argument      , nullptr, 'V'},
    {nullptr,            0                , nullptr,  0}
 };
 
@@ -173,6 +175,9 @@ parseArgs(int argc, char **argv) {
                 params.bootstrap.second = std::to_string(DHT_DEFAULT_PORT);
             }
             break;
+        case 'V':
+            params.version = true;
+            break;
         case 'h':
             params.help = true;
             break;