diff --git a/configure.ac b/configure.ac
index a6e2cebd3691f3c062ddd07f377d4cd879916704..e3581664a8cca0c07261f21607a8d22d49bb8026 100644
--- a/configure.ac
+++ b/configure.ac
@@ -704,7 +704,8 @@ AC_CONFIG_FILES([Makefile \
                  src/upnp/Makefile \
                  ringtones/Makefile \
                  test/Makefile\
-                 test/sip/Makefile \
+                 test/sip/Makefile
+                 test/turn/Makefile \
                  test/unitTest/Makefile \
 
                  man/Makefile \
diff --git a/src/Makefile.am b/src/Makefile.am
index dc81ed18e914e97bda2f3d8ccf00baf6a6ba7f13..f39d30bfb99fe980ac1514707fa5b8aebc44e466 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -140,7 +140,9 @@ libring_la_SOURCES = \
 		smartools.cpp \
 		smartools.h \
         base64.h \
-        base64.cpp
+        base64.cpp \
+        turn_transport.h \
+        turn_transport.cpp
 
 if HAVE_WIN32
 libring_la_SOURCES += \
diff --git a/src/turn_transport.cpp b/src/turn_transport.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..54355c826de63fdf76c967f69d0588c53def94d7
--- /dev/null
+++ b/src/turn_transport.cpp
@@ -0,0 +1,316 @@
+/*
+ *  Copyright (C) 2017 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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.
+ */
+
+#include "turn_transport.h"
+
+#include "logger.h"
+#include "ip_utils.h"
+#include "sip/sip_utils.h"
+
+#include <pjnath.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+#include <stdexcept>
+#include <future>
+#include <atomic>
+#include <thread>
+#include <vector>
+#include <iterator>
+#include <mutex>
+
+namespace ring {
+
+enum class RelayState {
+    NONE,
+    READY,
+    DOWN,
+};
+
+class TurnTransportPimpl {
+public:
+    TurnTransportPimpl() = default;
+    ~TurnTransportPimpl();
+
+    void onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state);
+    void onRxData(uint8_t* pkt, unsigned pkt_len, const pj_sockaddr_t* peer_addr, unsigned addr_len);
+    void onPeerConnection(pj_uint32_t conn_id, const pj_sockaddr_t* peer_addr, unsigned addr_len);
+    void ioJob();
+
+    TurnTransportParams settings;
+    pj_caching_pool poolCache {};
+    pj_pool_t* pool {nullptr};
+    pj_stun_config stunConfig {};
+    pj_turn_sock* relay {nullptr};
+    pj_str_t relayAddr {};
+    IpAddr peerRelayAddr; // address where peers should connect to
+    IpAddr mappedAddr;
+
+    std::map<IpAddr, std::vector<char>> streams;
+    std::mutex streamsMutex;
+
+    std::atomic<RelayState> state {RelayState::NONE};
+    std::atomic_bool ioJobQuit {false};
+    std::thread ioWorker;
+};
+
+TurnTransportPimpl::~TurnTransportPimpl()
+{
+    if (relay)
+        pj_turn_sock_destroy(relay);
+    ioJobQuit = true;
+    if (ioWorker.joinable())
+        ioWorker.join();
+    if (pool)
+        pj_pool_release(pool);
+    pj_caching_pool_destroy(&poolCache);
+}
+
+void
+TurnTransportPimpl::onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state)
+{
+    if (new_state == PJ_TURN_STATE_READY) {
+        pj_turn_session_info info;
+        pj_turn_sock_get_info(relay, &info);
+        peerRelayAddr = IpAddr {info.relay_addr};
+        mappedAddr = IpAddr {info.mapped_addr};
+        RING_DBG("TURN server ready, peer relay address: %s", peerRelayAddr.toString(true, true).c_str());
+        state = RelayState::READY;
+    } else if (old_state <= PJ_TURN_STATE_READY and new_state > PJ_TURN_STATE_READY) {
+        RING_WARN("TURN server disconnected (%s)", pj_turn_state_name(new_state));
+        state = RelayState::DOWN;
+    }
+}
+
+void
+TurnTransportPimpl::onRxData(uint8_t* pkt, unsigned pkt_len,
+                             const pj_sockaddr_t* addr, unsigned addr_len)
+{
+    IpAddr peer_addr ( *static_cast<const pj_sockaddr*>(addr), addr_len );
+
+    std::lock_guard<std::mutex> lk {streamsMutex};
+    auto& vec = streams[peer_addr];
+    vec.insert(vec.cend(), pkt, pkt + pkt_len);
+}
+
+void
+TurnTransportPimpl::onPeerConnection(pj_uint32_t conn_id,
+                                     const pj_sockaddr_t* addr, unsigned addr_len)
+{
+    IpAddr peer_addr ( *static_cast<const pj_sockaddr*>(addr), addr_len );
+    RING_DBG("Received connection attempt from %s, id=%x",
+             peer_addr.toString(true, true).c_str(), conn_id);
+    {
+        std::lock_guard<std::mutex> lk {streamsMutex};
+        streams[peer_addr].clear();
+    }
+    pj_turn_connect_peer(relay, conn_id, addr, addr_len);
+    if (settings.onPeerConnection)
+        settings.onPeerConnection(conn_id, peer_addr);
+}
+
+void
+TurnTransportPimpl::ioJob()
+{
+    sip_utils::register_thread();
+
+    while (!ioJobQuit.load()) {
+        const pj_time_val delay = {0, 10};
+        pj_ioqueue_poll(stunConfig.ioqueue, &delay);
+        pj_timer_heap_poll(stunConfig.timer_heap, nullptr);
+  }
+}
+
+class PjsipError final : public std::exception {
+public:
+    PjsipError() = default;
+    explicit PjsipError(pj_status_t st) : std::exception() {
+        char err_msg[PJ_ERR_MSG_SIZE];
+        pj_strerror(st, err_msg, sizeof(err_msg));
+        what_msg_ += ": ";
+        what_msg_ += err_msg;
+    }
+    const char* what() const noexcept override {
+        return what_msg_.c_str();
+    };
+private:
+    std::string what_msg_ {"PJSIP api error"};
+};
+
+template <class Callable, class... Args>
+inline void PjsipCall(Callable& func, Args... args)
+{
+    auto status = func(args...);
+    if (status != PJ_SUCCESS)
+        throw PjsipError(status);
+}
+
+template <class Callable, class... Args>
+inline auto PjsipCallReturn(const Callable& func, Args... args) -> decltype(func(args...))
+{
+    auto res = func(args...);
+    if (!res)
+        throw PjsipError();
+    return res;
+}
+
+//==================================================================================================
+
+TurnTransport::TurnTransport(const TurnTransportParams& params)
+    : pimpl_ {new TurnTransportPimpl}
+{
+    auto server = params.server;
+    if (!server.getPort())
+        server.setPort(PJ_STUN_PORT);
+
+    if (server.isUnspecified())
+        throw std::invalid_argument("invalid turn server address");
+
+    pimpl_->settings = params;
+
+    // PJSIP memory pool
+    pj_caching_pool_init(&pimpl_->poolCache, &pj_pool_factory_default_policy, 0);
+    pimpl_->pool = PjsipCallReturn(pj_pool_create, &pimpl_->poolCache.factory,
+                                   "RgTurnTr", 512, 512, nullptr);
+
+    // STUN config
+    pj_stun_config_init(&pimpl_->stunConfig, &pimpl_->poolCache.factory, 0, nullptr, nullptr);
+
+    // create global timer heap
+    PjsipCall(pj_timer_heap_create, pimpl_->pool, 1000, &pimpl_->stunConfig.timer_heap);
+
+    // create global ioqueue
+    PjsipCall(pj_ioqueue_create, pimpl_->pool, 16, &pimpl_->stunConfig.ioqueue);
+
+    // run a thread to handles timer/ioqueue events
+    pimpl_->ioWorker = std::thread([this]{ pimpl_->ioJob(); });
+
+    // TURN callbacks
+    pj_turn_sock_cb relay_cb;
+    pj_bzero(&relay_cb, sizeof(relay_cb));
+    relay_cb.on_rx_data = [](pj_turn_sock* relay, void* pkt, unsigned pkt_len,
+                             const pj_sockaddr_t* peer_addr, unsigned addr_len) {
+        auto tr = static_cast<TurnTransport*>(pj_turn_sock_get_user_data(relay));
+        tr->pimpl_->onRxData(reinterpret_cast<uint8_t*>(pkt), pkt_len, peer_addr, addr_len);
+    };
+    relay_cb.on_state = [](pj_turn_sock* relay, pj_turn_state_t old_state,
+                           pj_turn_state_t new_state) {
+        auto tr = static_cast<TurnTransport*>(pj_turn_sock_get_user_data(relay));
+        tr->pimpl_->onTurnState(old_state, new_state);
+    };
+    relay_cb.on_peer_connection = [](pj_turn_sock* relay, pj_uint32_t conn_id,
+                                     const pj_sockaddr_t* peer_addr, unsigned addr_len){
+        auto tr = static_cast<TurnTransport*>(pj_turn_sock_get_user_data(relay));
+        tr->pimpl_->onPeerConnection(conn_id, peer_addr, addr_len);
+    };
+
+    // TURN socket config
+    pj_turn_sock_cfg turn_sock_cfg;
+    pj_turn_sock_cfg_default(&turn_sock_cfg);
+    turn_sock_cfg.max_pkt_size = params.maxPacketSize;
+
+    // TURN socket creation
+    PjsipCall(pj_turn_sock_create,
+              &pimpl_->stunConfig, server.getFamily(), PJ_TURN_TP_TCP,
+              &relay_cb, &turn_sock_cfg, this, &pimpl_->relay);
+
+    // TURN allocation setup
+    pj_turn_alloc_param turn_alloc_param;
+	pj_turn_alloc_param_default(&turn_alloc_param);
+
+    if (params.isPeerConnection)
+        turn_alloc_param.peer_conn_type = PJ_TURN_TP_TCP; // RFC 6062!!!
+
+    pj_stun_auth_cred cred;
+    pj_bzero(&cred, sizeof(cred));
+    cred.type = PJ_STUN_AUTH_CRED_STATIC;
+    pj_cstr(&cred.data.static_cred.realm, params.realm.c_str());
+    pj_cstr(&cred.data.static_cred.username, params.username.c_str());
+    cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+    pj_cstr(&cred.data.static_cred.data, params.password.c_str());
+
+    pimpl_->relayAddr = pj_strdup3(pimpl_->pool, server.toString().c_str());
+
+    // TURN connection/allocation
+    RING_DBG() << "Connecting to TURN " << server.toString(true, true);
+    PjsipCall(pj_turn_sock_alloc,
+              pimpl_->relay, &pimpl_->relayAddr, server.getPort(),
+              nullptr, &cred, &turn_alloc_param);
+}
+
+TurnTransport::~TurnTransport()
+{}
+
+void
+TurnTransport::permitPeer(const IpAddr& addr)
+{
+    if (addr.isUnspecified())
+        throw std::invalid_argument("invalid peer address");
+
+    PjsipCall(pj_turn_sock_set_perm, pimpl_->relay, 1, addr.pjPtr(), 1);
+}
+
+bool
+TurnTransport::isReady() const
+{
+    return pimpl_->state.load() == RelayState::READY;
+}
+
+void
+TurnTransport::waitServerReady()
+{
+    while (pimpl_->state.load() != RelayState::READY) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(250));
+    }
+}
+
+const IpAddr&
+TurnTransport::peerRelayAddr() const
+{
+    return pimpl_->peerRelayAddr;
+}
+
+const IpAddr&
+TurnTransport::mappedAddr() const
+{
+    return pimpl_->mappedAddr;
+}
+
+bool
+TurnTransport::sendto(const IpAddr& peer, const std::vector<char>& buffer)
+{
+    auto status = pj_turn_sock_sendto(pimpl_->relay,
+                                      reinterpret_cast<const pj_uint8_t*>(buffer.data()), buffer.size(),
+                                      peer.pjPtr(), peer.getLength());
+    if (status != PJ_SUCCESS && status != PJ_EPENDING)
+        throw PjsipError(status);
+
+    return status == PJ_SUCCESS;
+}
+
+void
+TurnTransport::recvfrom(std::map<IpAddr, std::vector<char>>& streams)
+{
+    std::lock_guard<std::mutex> lk {pimpl_->streamsMutex};
+    streams = std::move(pimpl_->streams);
+    pimpl_->streams.clear();
+}
+
+} // namespace ring
diff --git a/src/turn_transport.h b/src/turn_transport.h
new file mode 100644
index 0000000000000000000000000000000000000000..698229676fe68b37110d8144396c9df75f209e50
--- /dev/null
+++ b/src/turn_transport.h
@@ -0,0 +1,111 @@
+/*
+ *  Copyright (C) 2017 Savoir-faire Linux Inc.
+ *
+ *  Author: Guillaume Roguez <guillaume.roguez@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 "ip_utils.h"
+
+#include <string>
+#include <memory>
+#include <functional>
+#include <map>
+
+namespace ring {
+
+class TurnTransportPimpl;
+
+struct TurnTransportParams {
+    IpAddr server;
+
+    // Plain Credentials
+    std::string realm;
+    std::string username;
+    std::string password;
+
+    bool isPeerConnection {false};
+    uint32_t connectionId {0};
+    std::function<void(uint32_t conn_id, const IpAddr& peer_addr)> onPeerConnection;
+
+    std::size_t maxPacketSize {3000}; ///< size of one "logical" packet
+};
+
+class TurnTransport {
+public:
+    ///
+    /// Constructs a TurnTransport connected by TCP to given server.
+    ///
+    /// Throw std::invalid_argument of peer address is invalid.
+    ///
+    /// \param param parameters to setup the transport
+    ///
+    /// \note If TURN server port is not set, the default TURN port 3478 (RFC5766) is used.
+    ///
+    TurnTransport(const TurnTransportParams& param);
+
+    ~TurnTransport();
+
+    ///
+    /// Wait for successful connection on the TURN server.
+    ///
+    /// TurnTransport constructor connects asynchronously on the TURN server.
+    /// You need to wait the READY state before calling any other APIs.
+    ///
+    void waitServerReady();
+
+    bool isReady() const;
+
+    const IpAddr& peerRelayAddr() const;
+    const IpAddr& mappedAddr() const;
+
+    ///
+    /// Gives server access permission to given peer by its address.
+    ///
+    /// Throw std::invalid_argument of peer address is invalid.
+    /// Throw std::runtime_error if case of backend errors.
+    ///
+    /// \param addr peer address
+    ///
+    /// \note The peer address family must be same as the turn server.
+    /// \note Must be called only if server is ready.
+    /// \see waitServerReady
+    ///
+    void permitPeer(const IpAddr& addr);
+
+    ///
+    /// Collect pending data.
+    ///
+    void recvfrom(std::map<IpAddr, std::vector<char>>& streams);
+
+    ///
+    /// Send data to a given peer through the TURN tunnel.
+    ///
+    bool sendto(const IpAddr& peer, const std::vector<char>& data);
+
+public:
+    // Move semantic
+    TurnTransport(TurnTransport&&) = default;
+    TurnTransport& operator=(TurnTransport&&) = default;
+
+private:
+    TurnTransport() = delete;
+    std::unique_ptr<TurnTransportPimpl> pimpl_;
+};
+
+} // namespace ring
diff --git a/test/.gitignore b/test/.gitignore
index c166fb3d8ffc09ce07dba3f691763f198147e541..df1c4422f0eaafdee754347ec4b08bcc6a8a1393 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -2,7 +2,6 @@
 
 # test binaries
 ut_*
-test_*
 
 # test result files
 *.log
diff --git a/test/Makefile.am b/test/Makefile.am
index 19aad19f54e3caeda6c670f4999833bb0ea111e9..27eb42781ab400f40a65fa6e9ea81bfa65464d1e 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = sip
+SUBDIRS = sip turn
 SUBDIRS += unitTest
diff --git a/test/turn/Makefile.am b/test/turn/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..5f419219d2129fbb1c4098035ca7d7eb23eb143c
--- /dev/null
+++ b/test/turn/Makefile.am
@@ -0,0 +1,10 @@
+# Rules for the test code (use `make check` to execute)
+include $(top_srcdir)/globals.mk
+
+AM_CXXFLAGS = -I$(top_srcdir)/src
+AM_LDFLAGS = $(CPPUNIT_LIBS) $(top_builddir)/src/libring.la
+
+check_PROGRAMS = test_turn
+test_turn_SOURCES = main.cpp test_turn.h test_TURN.cpp
+
+TESTS = $(check_PROGRAMS)
diff --git a/test/turn/main.cpp b/test/turn/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1cf4bcdd2dc04731d1c06ba08ee7c134f5c72c15
--- /dev/null
+++ b/test/turn/main.cpp
@@ -0,0 +1,60 @@
+/*
+ *  Copyright (C) 2017 Savoir-Faire Linux Inc.
+ *  Author: Guillaume Roguez <guillaume.roguez@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.
+ */
+
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/CompilerOutputter.h>
+
+#include "dring.h"
+
+#include <stdexcept>
+
+void init_daemon()
+{
+    DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG));
+    DRing::start("dring-sample.yml");
+}
+
+int main()
+{
+    init_daemon();
+
+    CppUnit::TextUi::TestRunner runner;
+
+    // Register all tests
+    auto& registry = CppUnit::TestFactoryRegistry::getRegistry();
+    runner.addTest(registry.makeTest());
+
+    // Use a compiler error format outputter for results and output into stderr
+    runner.setOutputter(new CppUnit::CompilerOutputter(&runner.result(), std::cerr ));
+
+    bool ret;
+
+    try {
+        // Run tests
+        ret = !runner.run("", false);
+    } catch (const std::exception& e) {
+        std::cerr << "Exception catched during tests: " << e.what() << '\n';
+        ret = 1;
+    }
+
+    DRing::fini();
+
+    return ret;
+}
diff --git a/test/turn/test_TURN.cpp b/test/turn/test_TURN.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8f7494f3e9db7a8d8db01174a775e42f397bc860
--- /dev/null
+++ b/test/turn/test_TURN.cpp
@@ -0,0 +1,137 @@
+/*
+ *  Copyright (C) 2017 Savoir-Faire Linux Inc.
+ *  Author: Guillaume Roguez <guillaume.roguez@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.
+ */
+
+#include "test_TURN.h"
+#include "turn_transport.h"
+
+#include <sys/socket.h>
+#include <sys/unistd.h>
+#include <stdexcept>
+#include <thread>
+#include <chrono>
+#include <vector>
+
+using namespace ring;
+
+CPPUNIT_TEST_SUITE_REGISTRATION( test_TURN );
+
+class TCPSocket {
+public:
+    TCPSocket(int fa) {
+        sock_ = ::socket(fa, SOCK_STREAM, 0);
+        if (sock_ < 0)
+            throw std::system_error(errno, std::system_category());
+        IpAddr bound {"0.0.0.0"};
+        ::bind(sock_, bound, bound.getLength());
+    }
+
+    ~TCPSocket() {
+        if (sock_ >= 0)
+            ::close(sock_);
+    }
+
+    void connect(const IpAddr& addr) {
+        if (::connect(sock_, addr, addr.getLength()) < 0)
+            throw std::system_error(errno, std::system_category());
+    }
+
+    void send(const std::string& pkt) {
+        if (::send(sock_, pkt.data(), pkt.size(), 0) < 0)
+            throw std::system_error(errno, std::system_category());
+    }
+
+    void send(const std::vector<char>& pkt) {
+        if (::send(sock_, pkt.data(), pkt.size(), 0) < 0)
+            throw std::system_error(errno, std::system_category());
+    }
+
+    std::vector<char> recv(std::size_t max_len) {
+        std::vector<char> pkt(max_len);
+        auto rs = ::recv(sock_, pkt.data(), pkt.size(), 0);
+        if (rs < 0)
+            throw std::system_error(errno, std::system_category());
+        pkt.resize(rs);
+        pkt.shrink_to_fit();
+        return pkt;
+    }
+
+private:
+    int sock_ {-1};
+};
+
+void
+test_TURN::testSimpleConnection()
+{
+    TurnTransportParams param;
+    param.server = IpAddr {"turn.ring.cx"};
+    param.realm = "ring";
+    param.username = "ring";
+    param.password = "ring";
+    param.isPeerConnection = true;
+
+    TurnTransport turn {param};
+    turn.waitServerReady();
+
+    TCPSocket sock = {param.server.getFamily()};
+
+    // Permit myself
+    turn.permitPeer(turn.mappedAddr());
+    sock.connect(turn.peerRelayAddr());
+
+    std::string test_data = "Hello, World!";
+    sock.send(test_data);
+
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+
+    std::map<IpAddr, std::vector<char>> streams;
+    turn.recvfrom(streams);
+    CPPUNIT_ASSERT(streams.size() == 1);
+
+    auto peer_addr = std::begin(streams)->first;
+    const auto& vec = std::begin(streams)->second;
+    CPPUNIT_ASSERT(std::string(std::begin(vec), std::end(vec)) == test_data);
+
+    turn.recvfrom(streams);
+    CPPUNIT_ASSERT(streams.size() == 0);
+
+    turn.sendto(peer_addr, std::vector<char>{1, 2, 3, 4});
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+
+    auto res = sock.recv(1000);
+    CPPUNIT_ASSERT(res.size() == 4);
+
+#if 0
+    // DISABLED SECTION
+    // This code higly load the network and can be long to execute.
+    // Only kept for manual testing purpose.
+    std::vector<char> big(100000);
+    using clock = std::chrono::high_resolution_clock;
+
+    auto t1 = clock::now();
+    sock.send(big);
+    auto t2 = clock::now();
+
+    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(t2-t1).count();
+    std::cout << "T= " << duration << "ns"
+              << ", V= " << (8000. * big.size() / duration) << "Mb/s"
+              << " / " << (1000. * big.size() / duration) << "MB/s"
+              << '\n';
+    std::this_thread::sleep_for(std::chrono::seconds(5));
+#endif
+}
diff --git a/test/turn/test_TURN.h b/test/turn/test_TURN.h
new file mode 100644
index 0000000000000000000000000000000000000000..356be5be86023c1d14f96c96855055ac19b634cc
--- /dev/null
+++ b/test/turn/test_TURN.h
@@ -0,0 +1,45 @@
+/*
+ *  Copyright (C) 2017 Savoir-Faire Linux Inc.
+ *  Author: Guillaume Roguez <guillaume.roguez@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
+
+// Cppunit import
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/TestCaller.h>
+#include <cppunit/TestCase.h>
+#include <cppunit/TestSuite.h>
+
+
+/*
+ * @file test_TURN.h
+ * @brief Regroups unitary tests related to the TURN transport class
+ */
+
+class test_TURN : public CppUnit::TestFixture
+{
+private:
+    void testSimpleConnection(void);
+
+    /**
+     * Use cppunit library macros to add unit test to the factory
+     */
+    CPPUNIT_TEST_SUITE(test_TURN);
+    CPPUNIT_TEST(testSimpleConnection);
+    CPPUNIT_TEST_SUITE_END();
+};