diff --git a/include/opendht/dht.h b/include/opendht/dht.h
index 5b50aefac2dc8f2fa4980b803f6eec88871d243d..dd556c0c7275330c0096fc36d248e84fc012cdcd 100644
--- a/include/opendht/dht.h
+++ b/include/opendht/dht.h
@@ -27,11 +27,9 @@ THE SOFTWARE.
 
 #pragma once
 
-#define WANT4 1
-#define WANT6 2
-
 #include "infohash.h"
 #include "value.h"
+#include "network_engine.h"
 
 #include <string>
 #include <array>
@@ -45,73 +43,6 @@ THE SOFTWARE.
 
 namespace dht {
 
-using want_t = int_fast8_t;
-using Address = std::pair<sockaddr_storage, socklen_t>;
-
-std::string print_addr(const sockaddr* sa, socklen_t slen);
-std::string print_addr(const sockaddr_storage& ss, socklen_t sslen);
-std::string printAddr(const Address& addr);
-
-struct NodeExport {
-    InfoHash id;
-    sockaddr_storage ss;
-    socklen_t sslen;
-};
-
-struct Node {
-    InfoHash id {};
-    sockaddr_storage ss;
-    socklen_t sslen {0};
-    time_point time {time_point::min()};            /* last time eared about */
-    time_point reply_time {time_point::min()};      /* time of last correct reply received */
-    time_point pinged_time {time_point::min()};     /* time of last message sent */
-    unsigned pinged {0};           /* how many requests we sent since last reply */
-
-    Node() : ss() {
-        std::fill_n((uint8_t*)&ss, sizeof(ss), 0);
-    }
-    Node(const InfoHash& id, const sockaddr* sa, socklen_t salen)
-        : id(id), ss(), sslen(salen) {
-        std::copy_n((const uint8_t*)sa, salen, (uint8_t*)&ss);
-        if ((unsigned)salen < sizeof(ss))
-            std::fill_n((uint8_t*)&ss+salen, sizeof(ss)-salen, 0);
-    }
-    InfoHash getId() const {
-        return id;
-    }
-    std::pair<const sockaddr*, socklen_t> getAddr() const {
-        return {(const sockaddr*)&ss, sslen};
-    }
-    std::string getAddrStr() const {
-        return print_addr(ss, sslen);
-    }
-    bool isExpired(time_point now) const;
-    bool isExpired() const { return isExpired(clock::now()); }
-    bool isGood(time_point now) const;
-    bool isMessagePending(time_point now) const;
-    NodeExport exportNode() const { return NodeExport {id, ss, sslen}; }
-    sa_family_t getFamily() const { return ss.ss_family; }
-
-    void update(const sockaddr* sa, socklen_t salen);
-
-    /** To be called when a message was sent to the node */
-    void requested(time_point now);
-
-    /** To be called when a message was received from the node.
-     Answer should be true if the message was an aswer to a request we made*/
-    void received(time_point now, bool answer);
-
-    friend std::ostream& operator<< (std::ostream& s, const Node& h);
-
-    static constexpr const std::chrono::minutes NODE_GOOD_TIME {120};
-
-    /* The time after which we consider a node to be expirable. */
-    static constexpr const std::chrono::minutes NODE_EXPIRE_TIME {10};
-
-    /* Time for a request to timeout */
-    static constexpr const std::chrono::seconds MAX_RESPONSE_TIME {3};
-};
-
 /**
  * Main Dht class.
  * Provides a Distributed Hash Table node.
@@ -193,7 +124,7 @@ public:
      * and an ID for the node.
      */
     Dht(int s, int s6, Config config);
-    virtual ~Dht();
+    virtual ~Dht() {}
 
     /**
      * Get the ID of the node.
diff --git a/include/opendht/network_engine.h b/include/opendht/network_engine.h
index e2b771d04eec8f0425674aba47c6caa0f5570051..657c1192ad776f699176ecf80beabf641262de1a 100644
--- a/include/opendht/network_engine.h
+++ b/include/opendht/network_engine.h
@@ -26,9 +26,9 @@ THE SOFTWARE.
 
 #pragma once
 
-#include "dht.h"
 #include "value.h"
 #include "infohash.h"
+#include "node.h"
 #include "utils.h"
 #include "rng.h"
 
diff --git a/include/opendht/node.h b/include/opendht/node.h
new file mode 100644
index 0000000000000000000000000000000000000000..ed7217c6e49fc9d7bcf8e4a25b5738582601fe97
--- /dev/null
+++ b/include/opendht/node.h
@@ -0,0 +1,96 @@
+/*
+Copyright (C) 2009-2014 Juliusz Chroboczek
+Copyright (C) 2014-2016 Savoir-faire Linux Inc.
+
+Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>,
+            Simon Désaulniers <sim.desaulniers@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#pragma once
+
+#include "utils.h"
+#include "infohash.h"
+
+#include <arpa/inet.h>
+
+namespace dht {
+
+struct NodeExport {
+    InfoHash id;
+    sockaddr_storage ss;
+    socklen_t sslen;
+};
+
+struct Node {
+    InfoHash id {};
+    sockaddr_storage ss;
+    socklen_t sslen {0};
+    time_point time {time_point::min()};            /* last time eared about */
+    time_point reply_time {time_point::min()};      /* time of last correct reply received */
+    time_point pinged_time {time_point::min()};     /* time of last message sent */
+    unsigned pinged {0};           /* how many requests we sent since last reply */
+
+    Node() : ss() {
+        std::fill_n((uint8_t*)&ss, sizeof(ss), 0);
+    }
+    Node(const InfoHash& id, const sockaddr* sa, socklen_t salen)
+        : id(id), ss(), sslen(salen) {
+            std::copy_n((const uint8_t*)sa, salen, (uint8_t*)&ss);
+            if ((unsigned)salen < sizeof(ss))
+                std::fill_n((uint8_t*)&ss+salen, sizeof(ss)-salen, 0);
+        }
+    InfoHash getId() const {
+        return id;
+    }
+    std::pair<const sockaddr*, socklen_t> getAddr() const {
+        return {(const sockaddr*)&ss, sslen};
+    }
+    std::string getAddrStr() const {
+        return print_addr(ss, sslen);
+    }
+    bool isExpired(time_point now) const;
+    bool isExpired() const { return isExpired(clock::now()); }
+    bool isGood(time_point now) const;
+    bool isMessagePending(time_point now) const;
+    NodeExport exportNode() const { return NodeExport {id, ss, sslen}; }
+    sa_family_t getFamily() const { return ss.ss_family; }
+
+    void update(const sockaddr* sa, socklen_t salen);
+
+    /** To be called when a message was sent to the node */
+    void requested(time_point now);
+
+    /** To be called when a message was received from the node.
+      Answer should be true if the message was an aswer to a request we made*/
+    void received(time_point now, bool answer);
+
+    friend std::ostream& operator<< (std::ostream& s, const Node& h);
+
+    static constexpr const std::chrono::minutes NODE_GOOD_TIME {120};
+
+    /* The time after which we consider a node to be expirable. */
+    static constexpr const std::chrono::minutes NODE_EXPIRE_TIME {10};
+
+    /* Time for a request to timeout */
+    static constexpr const std::chrono::seconds MAX_RESPONSE_TIME {3};
+};
+
+}
diff --git a/include/opendht/utils.h b/include/opendht/utils.h
index ea8e1f0c0cdf1843ea8b0d3555ae1e3d926ab03e..a63799337d724956dfe62fd0537114291cef0cf1 100644
--- a/include/opendht/utils.h
+++ b/include/opendht/utils.h
@@ -19,6 +19,9 @@
 
 #pragma once
 
+#define WANT4 1
+#define WANT6 2
+
 #include <msgpack.hpp>
 
 #include <chrono>
@@ -40,6 +43,13 @@ void erase_if(std::map<Key, Item>& map, const Condition& condition)
 
 namespace dht {
 
+using Address = std::pair<sockaddr_storage, socklen_t>;
+using want_t = int_fast8_t;
+
+std::string print_addr(const sockaddr* sa, socklen_t slen);
+std::string print_addr(const sockaddr_storage& ss, socklen_t sslen);
+std::string printAddr(const Address& addr);
+
 class DhtException : public std::runtime_error {
     public:
         DhtException(const std::string &str = "") :
diff --git a/src/dht.cpp b/src/dht.cpp
index 8f5a5633331fbe422f051081ef17912cc1b01056..ac965407a7667c909a349563cb63af4857d8b529 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -91,35 +91,6 @@ to_hex(const uint8_t *buf, size_t buflen)
     return s.str();
 }
 
-std::string
-dht::print_addr(const sockaddr* sa, socklen_t slen)
-{
-    char hbuf[NI_MAXHOST];
-    char sbuf[NI_MAXSERV];
-    std::stringstream out;
-    if (!getnameinfo(sa, slen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
-        if (sa->sa_family == AF_INET6)
-            out << "[" << hbuf << "]";
-        else
-            out << hbuf;
-        if (strcmp(sbuf, "0"))
-            out << ":" << sbuf;
-    } else
-        out << "[invalid address]";
-    return out.str();
-}
-
-std::string
-dht::print_addr(const sockaddr_storage& ss, socklen_t sslen)
-{
-    return print_addr((const sockaddr*)&ss, sslen);
-}
-
-std::string
-dht::printAddr(const Address& addr) {
-    return print_addr((const sockaddr*)&addr.first, addr.second);
-}
-
 template <class DT>
 static double
 print_dt(DT d) {
@@ -130,10 +101,6 @@ namespace dht {
 
 static constexpr InfoHash zeroes {};
 
-constexpr std::chrono::minutes Node::NODE_EXPIRE_TIME;
-constexpr std::chrono::minutes Node::NODE_GOOD_TIME;
-constexpr std::chrono::seconds Node::MAX_RESPONSE_TIME;
-
 constexpr std::chrono::seconds Dht::SEARCH_GET_STEP;
 constexpr std::chrono::minutes Dht::MAX_STORAGE_MAINTENANCE_EXPIRE_TIME;
 constexpr std::chrono::minutes Dht::SEARCH_EXPIRE_TIME;
@@ -372,63 +339,6 @@ Dht::findNode(const InfoHash& id, sa_family_t af) const
     return {};
 }
 
-/* This is our definition of a known-good node. */
-bool
-Node::isGood(time_point now) const
-{
-    return
-        not isExpired(now) &&
-        reply_time >= now - NODE_GOOD_TIME &&
-        time >= now - NODE_EXPIRE_TIME;
-}
-
-bool
-Node::isExpired(time_point now) const
-{
-    return pinged >= 3 && reply_time < pinged_time && pinged_time + MAX_RESPONSE_TIME < now;
-}
-
-bool
-Node::isMessagePending(time_point now) const
-{
-    return reply_time < pinged_time && pinged_time + MAX_RESPONSE_TIME > now;
-}
-
-void
-Node::update(const sockaddr* sa, socklen_t salen)
-{
-    std::copy_n((const uint8_t*)sa, salen, (uint8_t*)&ss);
-    sslen = salen;
-}
-
-/** To be called when a message was sent to the node */
-void
-Node::requested(time_point now)
-{
-    pinged++;
-    if (reply_time > pinged_time || pinged_time + MAX_RESPONSE_TIME < now)
-        pinged_time = now;
-}
-
-/** To be called when a message was received from the node.
- Answer should be true if the message was an aswer to a request we made*/
-void
-Node::received(time_point now, bool answer)
-{
-    time = now;
-    if (answer) {
-        pinged = 0;
-        reply_time = now;
-    }
-}
-
-std::ostream& operator<< (std::ostream& s, const Node& h)
-{
-    s << h.id << " " << print_addr(h.ss, h.sslen);
-    return s;
-}
-
-
 std::shared_ptr<Node>
 Dht::NodeCache::getNode(const InfoHash& id, sa_family_t family) {
     auto& list = family == AF_INET ? cache_4 : cache_6;
@@ -2208,9 +2118,6 @@ Dht::Dht(int s, int s6, Config config)
 }
 
 
-Dht::~Dht()
-{}
-
 /* Rate control for requests we receive. */
 bool
 Dht::rateLimit()
diff --git a/src/node.cpp b/src/node.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4bbc198208fc2f930df0a3b6f427fa77541c1b4c
--- /dev/null
+++ b/src/node.cpp
@@ -0,0 +1,84 @@
+/*
+Copyright (C) 2009-2014 Juliusz Chroboczek
+Copyright (C) 2014-2016 Savoir-faire Linux Inc.
+
+Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>,
+            Simon Désaulniers <sim.desaulniers@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#include "node.h"
+
+namespace dht {
+
+constexpr std::chrono::minutes Node::NODE_EXPIRE_TIME;
+constexpr std::chrono::minutes Node::NODE_GOOD_TIME;
+constexpr std::chrono::seconds Node::MAX_RESPONSE_TIME;
+
+/* This is our definition of a known-good node. */
+Node::isGood(time_point now) const
+{
+    return
+        not isExpired(now) &&
+        reply_time >= now - NODE_GOOD_TIME &&
+        time >= now - NODE_EXPIRE_TIME;
+}
+
+bool
+Node::isExpired(time_point now) const
+{
+    return pinged >= 3 && reply_time < pinged_time && pinged_time + MAX_RESPONSE_TIME < now;
+}
+
+bool
+Node::isMessagePending(time_point now) const
+{
+    return reply_time < pinged_time && pinged_time + MAX_RESPONSE_TIME > now;
+}
+
+void
+Node::update(const sockaddr* sa, socklen_t salen)
+{
+    std::copy_n((const uint8_t*)sa, salen, (uint8_t*)&ss);
+    sslen = salen;
+}
+
+/** To be called when a message was sent to the node */
+void
+Node::requested(time_point now)
+{
+    pinged++;
+    if (reply_time > pinged_time || pinged_time + MAX_RESPONSE_TIME < now)
+        pinged_time = now;
+}
+
+/** To be called when a message was received from the node.
+ Answer should be true if the message was an aswer to a request we made*/
+void
+Node::received(time_point now, bool answer)
+{
+    time = now;
+    if (answer) {
+        pinged = 0;
+        reply_time = now;
+    }
+}
+
+}
diff --git a/src/utils.cpp b/src/utils.cpp
index d49b34aa21f3d9bf8bb84dea53d324a2abfa3711..5f2ea2bae5ca83ab01892132f7126418270e48e7 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -21,6 +21,35 @@
 
 namespace dht {
 
+std::string
+dht::print_addr(const sockaddr* sa, socklen_t slen)
+{
+    char hbuf[NI_MAXHOST];
+    char sbuf[NI_MAXSERV];
+    std::stringstream out;
+    if (!getnameinfo(sa, slen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
+        if (sa->sa_family == AF_INET6)
+            out << "[" << hbuf << "]";
+        else
+            out << hbuf;
+        if (strcmp(sbuf, "0"))
+            out << ":" << sbuf;
+    } else
+        out << "[invalid address]";
+    return out.str();
+}
+
+std::string
+dht::print_addr(const sockaddr_storage& ss, socklen_t sslen)
+{
+    return print_addr((const sockaddr*)&ss, sslen);
+}
+
+std::string
+dht::printAddr(const Address& addr) {
+    return print_addr((const sockaddr*)&addr.first, addr.second);
+}
+
 time_point from_time_t(std::time_t t) {
     return clock::now() + (std::chrono::system_clock::from_time_t(t) - std::chrono::system_clock::now());
 }