diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1c2ad3163bf8b7a066665160e51992a67b9a539..560d91d10b1eba0187c9637009147868b2e93891 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,6 +35,7 @@ list (APPEND opendht_SOURCES
     src/node.cpp
     src/value.cpp
     src/dht.cpp
+    src/routing_table.cpp
     src/network_engine.cpp
     src/securedht.cpp
     src/dhtrunner.cpp
@@ -55,6 +56,7 @@ list (APPEND opendht_HEADERS
     include/opendht/node.h
     include/opendht/value.h
     include/opendht/dht.h
+    include/opendht/routing_table.h
     include/opendht/network_engine.h
     include/opendht/scheduler.h
     include/opendht/securedht.h
diff --git a/include/opendht/dht.h b/include/opendht/dht.h
index 4251aba96d05b2769c594483f79f8f07dee4005d..f193984a0103319f98225f5d871a88f5d4ae75a2 100644
--- a/include/opendht/dht.h
+++ b/include/opendht/dht.h
@@ -26,6 +26,7 @@
 #include "utils.h"
 #include "network_engine.h"
 #include "scheduler.h"
+#include "routing_table.h"
 
 #include <string>
 #include <array>
@@ -368,60 +369,6 @@ private:
         std::list<std::weak_ptr<Node>> cache_6;
     };
 
-    struct Bucket {
-        Bucket() : cached() {}
-        Bucket(sa_family_t af, const InfoHash& f = {}, time_point t = time_point::min())
-            : af(af), first(f), time(t), cached() {}
-        sa_family_t af {0};
-        InfoHash first {};
-        time_point time {time_point::min()};             /* time of last reply in this bucket */
-        std::list<std::shared_ptr<Node>> nodes {};
-        sockaddr_storage cached;  /* the address of a likely candidate */
-        socklen_t cachedlen {0};
-
-        /** Return a random node in a bucket. */
-        std::shared_ptr<Node> randomNode();
-    };
-
-    class RoutingTable : public std::list<Bucket> {
-    public:
-        using std::list<Bucket>::list;
-
-        InfoHash middle(const RoutingTable::const_iterator&) const;
-
-        std::vector<std::shared_ptr<Node>> findClosestNodes(const InfoHash id, time_point now, size_t count = TARGET_NODES) const;
-
-        RoutingTable::iterator findBucket(const InfoHash& id);
-        RoutingTable::const_iterator findBucket(const InfoHash& id) const;
-
-        /**
-         * Return true if the id is in the bucket's range.
-         */
-        inline bool contains(const RoutingTable::const_iterator& bucket, const InfoHash& id) const {
-            return InfoHash::cmp(bucket->first, id) <= 0
-                && (std::next(bucket) == end() || InfoHash::cmp(id, std::next(bucket)->first) < 0);
-        }
-
-        /**
-         * Return true if the table has no bucket ore one empty buket.
-         */
-        inline bool isEmpty() const {
-            return empty() || (size() == 1 && front().nodes.empty());
-        }
-
-        /**
-         * Return a random id in the bucket's range.
-         */
-        InfoHash randomId(const RoutingTable::const_iterator& bucket) const;
-
-        unsigned depth(const RoutingTable::const_iterator& bucket) const;
-
-        /**
-         * Split a bucket in two equal parts.
-         */
-        bool split(const RoutingTable::iterator& b);
-    };
-
     struct SearchNode {
         SearchNode(std::shared_ptr<Node> node) : node(node) {}
 
diff --git a/include/opendht/routing_table.h b/include/opendht/routing_table.h
new file mode 100644
index 0000000000000000000000000000000000000000..f78babd6870293ceaec992ec44491c3a87367fc9
--- /dev/null
+++ b/include/opendht/routing_table.h
@@ -0,0 +1,80 @@
+/*
+ *  Copyright (C) 2014-2016 Savoir-faire Linux Inc.
+ *  Author(s) : Adrien Béraud <adrien.beraud@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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "node.h"
+
+namespace dht {
+
+struct Bucket {
+    Bucket() : cached() {}
+    Bucket(sa_family_t af, const InfoHash& f = {}, time_point t = time_point::min())
+        : af(af), first(f), time(t), cached() {}
+    sa_family_t af {0};
+    InfoHash first {};
+    time_point time {time_point::min()};             /* time of last reply in this bucket */
+    std::list<std::shared_ptr<Node>> nodes {};
+    sockaddr_storage cached;  /* the address of a likely candidate */
+    socklen_t cachedlen {0};
+
+    /** Return a random node in a bucket. */
+    std::shared_ptr<Node> randomNode();
+};
+
+class RoutingTable : public std::list<Bucket> {
+public:
+    using std::list<Bucket>::list;
+
+    InfoHash middle(const RoutingTable::const_iterator&) const;
+
+    std::vector<std::shared_ptr<Node>> findClosestNodes(const InfoHash id, time_point now, size_t count = TARGET_NODES) const;
+
+    RoutingTable::iterator findBucket(const InfoHash& id);
+    RoutingTable::const_iterator findBucket(const InfoHash& id) const;
+
+    /**
+     * Return true if the id is in the bucket's range.
+     */
+    inline bool contains(const RoutingTable::const_iterator& bucket, const InfoHash& id) const {
+        return InfoHash::cmp(bucket->first, id) <= 0
+            && (std::next(bucket) == end() || InfoHash::cmp(id, std::next(bucket)->first) < 0);
+    }
+
+    /**
+     * Return true if the table has no bucket ore one empty buket.
+     */
+    inline bool isEmpty() const {
+        return empty() || (size() == 1 && front().nodes.empty());
+    }
+
+    /**
+     * Return a random id in the bucket's range.
+     */
+    InfoHash randomId(const RoutingTable::const_iterator& bucket) const;
+
+    unsigned depth(const RoutingTable::const_iterator& bucket) const;
+
+    /**
+     * Split a bucket in two equal parts.
+     */
+    bool split(const RoutingTable::iterator& b);
+};
+
+}
diff --git a/src/dht.cpp b/src/dht.cpp
index 60fe95440f91da4239c76c6b69e55063b83bdfcf..75bd32b203614dc6bb500bbec1844f2a93293856 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -173,128 +173,6 @@ Dht::isMartian(const sockaddr *sa, socklen_t len)
     }
 }
 
-std::shared_ptr<Node>
-Dht::Bucket::randomNode()
-{
-    if (nodes.empty())
-        return nullptr;
-    std::uniform_int_distribution<unsigned> rand_node(0, nodes.size()-1);
-    unsigned nn = rand_node(rd);
-    for (auto& n : nodes)
-        if (not nn--) return n;
-    return nodes.back();
-}
-
-InfoHash
-Dht::RoutingTable::randomId(const Dht::RoutingTable::const_iterator& it) const
-{
-    int bit1 = it->first.lowbit();
-    int bit2 = std::next(it) != end() ? std::next(it)->first.lowbit() : -1;
-    int bit = std::max(bit1, bit2) + 1;
-
-    if (bit >= 8*(int)HASH_LEN)
-        return it->first;
-
-    int b = bit/8;
-    InfoHash id_return;
-    std::copy_n(it->first.begin(), b, id_return.begin());
-    id_return[b] = it->first[b] & (0xFF00 >> (bit % 8));
-    id_return[b] |= rand_byte(rd) >> (bit % 8);
-    for (unsigned i = b + 1; i < HASH_LEN; i++)
-        id_return[i] = rand_byte(rd);
-    return id_return;
-}
-
-InfoHash
-Dht::RoutingTable::middle(const RoutingTable::const_iterator& it) const
-{
-    unsigned bit = depth(it);
-    if (bit >= 8*HASH_LEN)
-        throw std::out_of_range("End of table");
-
-    InfoHash id = it->first;
-    id.setBit(bit, 1);
-    return id;
-}
-
-unsigned
-Dht::RoutingTable::depth(const RoutingTable::const_iterator& it) const
-{
-    int bit1 = it->first.lowbit();
-    int bit2 = std::next(it) != end() ? std::next(it)->first.lowbit() : -1;
-    return std::max(bit1, bit2)+1;
-}
-
-std::vector<std::shared_ptr<Node>>
-Dht::RoutingTable::findClosestNodes(const InfoHash id, time_point now, size_t count) const
-{
-    std::vector<std::shared_ptr<Node>> nodes {};
-    auto bucket = findBucket(id);
-
-    if (bucket == end()) { return nodes; }
-
-    auto sortedBucketInsert = [&](const Bucket &b) {
-        for (auto n : b.nodes) {
-            if (not n->isGood(now))
-                continue;
-
-            auto here = std::find_if(nodes.begin(), nodes.end(),
-                [&id,&n](std::shared_ptr<Node> &node) {
-                    return id.xorCmp(n->id, node->id) < 0;
-                }
-            );
-            nodes.insert(here, n);
-        }
-    };
-
-    auto itn = bucket;
-    auto itp = std::prev(bucket);
-    while (nodes.size() < count && (itn != end() || itp != end())) {
-        if (itn != end()) {
-            sortedBucketInsert(*itn);
-            itn = std::next(itn);
-        }
-        if (itp != end()) {
-            sortedBucketInsert(*itp);
-            if (itp == begin()) {
-                itp = end();
-                continue;
-            }
-            itp = std::prev(itp);
-        }
-    }
-
-    // shrink to the count closest nodes.
-    if (nodes.size() > count) {
-        nodes.resize(count);
-    }
-    return nodes;
-}
-
-Dht::RoutingTable::iterator
-Dht::RoutingTable::findBucket(const InfoHash& id)
-{
-    if (empty())
-        return end();
-    auto b = begin();
-    while (true) {
-        auto next = std::next(b);
-        if (next == end())
-            return b;
-        if (InfoHash::cmp(id, next->first) < 0)
-            return b;
-        b = next;
-    }
-}
-
-Dht::RoutingTable::const_iterator
-Dht::RoutingTable::findBucket(const InfoHash& id) const
-{
-    /* Avoid code duplication for the const version */
-    const_iterator it = const_cast<RoutingTable*>(this)->findBucket(id);
-    return it;
-}
-
 /* Every bucket contains an unordered list of nodes. */
 std::shared_ptr<Node>
 Dht::findNode(const InfoHash& id, sa_family_t af)
@@ -444,34 +322,6 @@ Dht::getPublicAddress(sa_family_t family)
     return ret;
 }
 
-/* Split a bucket into two equal parts. */
-bool
-Dht::RoutingTable::split(const RoutingTable::iterator& b)
-{
-    InfoHash new_id;
-    try {
-        new_id = middle(b);
-    } catch (const std::out_of_range& e) {
-        return false;
-    }
-
-    // Insert new bucket
-    insert(std::next(b), Bucket {b->af, new_id, b->time});
-
-    // Re-assign nodes
-    std::list<std::shared_ptr<Node>> nodes {};
-    nodes.splice(nodes.begin(), b->nodes);
-    while (!nodes.empty()) {
-        auto n = nodes.begin();
-        auto b = findBucket((*n)->id);
-        if (b == end())
-            nodes.erase(n);
-        else
-            b->nodes.splice(b->nodes.begin(), nodes, n);
-    }
-    return true;
-}
-
 bool
 Dht::trySearchInsert(const std::shared_ptr<Node>& node)
 {
diff --git a/src/routing_table.cpp b/src/routing_table.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e8873b682356441ec95f4b619347d000247e12ec
--- /dev/null
+++ b/src/routing_table.cpp
@@ -0,0 +1,161 @@
+#include "routing_table.h"
+#include "rng.h"
+
+#include <memory>
+
+namespace dht {
+
+static std::mt19937 rd {dht::crypto::random_device{}()};
+static std::uniform_int_distribution<uint8_t> rand_byte;
+
+std::shared_ptr<Node>
+Bucket::randomNode()
+{
+    if (nodes.empty())
+        return nullptr;
+    std::uniform_int_distribution<unsigned> rand_node(0, nodes.size()-1);
+    unsigned nn = rand_node(rd);
+    for (auto& n : nodes)
+        if (not nn--) return n;
+    return nodes.back();
+}
+
+InfoHash
+RoutingTable::randomId(const RoutingTable::const_iterator& it) const
+{
+    int bit1 = it->first.lowbit();
+    int bit2 = std::next(it) != end() ? std::next(it)->first.lowbit() : -1;
+    int bit = std::max(bit1, bit2) + 1;
+
+    if (bit >= 8*(int)HASH_LEN)
+        return it->first;
+
+    int b = bit/8;
+    InfoHash id_return;
+    std::copy_n(it->first.begin(), b, id_return.begin());
+    id_return[b] = it->first[b] & (0xFF00 >> (bit % 8));
+    id_return[b] |= rand_byte(rd) >> (bit % 8);
+    for (unsigned i = b + 1; i < HASH_LEN; i++)
+        id_return[i] = rand_byte(rd);
+    return id_return;
+}
+
+InfoHash
+RoutingTable::middle(const RoutingTable::const_iterator& it) const
+{
+    unsigned bit = depth(it);
+    if (bit >= 8*HASH_LEN)
+        throw std::out_of_range("End of table");
+
+    InfoHash id = it->first;
+    id.setBit(bit, 1);
+    return id;
+}
+
+unsigned
+RoutingTable::depth(const RoutingTable::const_iterator& it) const
+{
+    int bit1 = it->first.lowbit();
+    int bit2 = std::next(it) != end() ? std::next(it)->first.lowbit() : -1;
+    return std::max(bit1, bit2)+1;
+}
+
+std::vector<std::shared_ptr<Node>>
+RoutingTable::findClosestNodes(const InfoHash id, time_point now, size_t count) const
+{
+    std::vector<std::shared_ptr<Node>> nodes {};
+    auto bucket = findBucket(id);
+
+    if (bucket == end()) { return nodes; }
+
+    auto sortedBucketInsert = [&](const Bucket &b) {
+        for (auto n : b.nodes) {
+            if (not n->isGood(now))
+                continue;
+
+            auto here = std::find_if(nodes.begin(), nodes.end(),
+                [&id,&n](std::shared_ptr<Node> &node) {
+                    return id.xorCmp(n->id, node->id) < 0;
+                }
+            );
+            nodes.insert(here, n);
+        }
+    };
+
+    auto itn = bucket;
+    auto itp = std::prev(bucket);
+    while (nodes.size() < count && (itn != end() || itp != end())) {
+        if (itn != end()) {
+            sortedBucketInsert(*itn);
+            itn = std::next(itn);
+        }
+        if (itp != end()) {
+            sortedBucketInsert(*itp);
+            if (itp == begin()) {
+                itp = end();
+                continue;
+            }
+            itp = std::prev(itp);
+        }
+    }
+
+    // shrink to the count closest nodes.
+    if (nodes.size() > count) {
+        nodes.resize(count);
+    }
+    return nodes;
+}
+
+RoutingTable::iterator
+RoutingTable::findBucket(const InfoHash& id)
+{
+    if (empty())
+        return end();
+    auto b = begin();
+    while (true) {
+        auto next = std::next(b);
+        if (next == end())
+            return b;
+        if (InfoHash::cmp(id, next->first) < 0)
+            return b;
+        b = next;
+    }
+}
+
+RoutingTable::const_iterator
+RoutingTable::findBucket(const InfoHash& id) const
+{
+    /* Avoid code duplication for the const version */
+    const_iterator it = const_cast<RoutingTable*>(this)->findBucket(id);
+    return it;
+}
+
+/* Split a bucket into two equal parts. */
+bool
+RoutingTable::split(const RoutingTable::iterator& b)
+{
+    InfoHash new_id;
+    try {
+        new_id = middle(b);
+    } catch (const std::out_of_range& e) {
+        return false;
+    }
+
+    // Insert new bucket
+    insert(std::next(b), Bucket {b->af, new_id, b->time});
+
+    // Re-assign nodes
+    std::list<std::shared_ptr<Node>> nodes {};
+    nodes.splice(nodes.begin(), b->nodes);
+    while (!nodes.empty()) {
+        auto n = nodes.begin();
+        auto b = findBucket((*n)->id);
+        if (b == end())
+            nodes.erase(n);
+        else
+            b->nodes.splice(b->nodes.begin(), nodes, n);
+    }
+    return true;
+}
+
+}