diff --git a/CMakeLists.txt b/CMakeLists.txt
index b3a402180cadc90d3bc2b8c186e0fd2ae4afa319..df83e7a07ebff06ba53a6b274b1c29fffc6c3131 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -149,6 +149,7 @@ list (APPEND opendht_SOURCES
     src/securedht.cpp
     src/dhtrunner.cpp
     src/log.cpp
+    src/peer_discovery.cpp
 )
 
 list (APPEND opendht_HEADERS
@@ -172,6 +173,7 @@ list (APPEND opendht_HEADERS
     include/opendht/securedht.h
     include/opendht/log.h
     include/opendht/log_enable.h
+    include/opendht/peer_discovery.h
     include/opendht.h
 )
 
@@ -314,6 +316,8 @@ if (OPENDHT_TESTS)
       tests/cryptotester.cpp
       tests/dhtrunnertester.h
       tests/dhtrunnertester.cpp
+      tests/peerdiscoverytester.h
+      tests/peerdiscoverytester.cpp
     )
     if (OPENDHT_PROXY_SERVER AND OPENDHT_PROXY_CLIENT)
       list (APPEND test_FILES
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index a377c5c326ed34d22e33867db3fab0cd56b24485..5b942760aadd825d1c09fae71bee55cf301db12a 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -40,6 +40,7 @@ namespace dht {
 
 struct Node;
 class SecureDht;
+class PeerDiscovery;
 struct SecureDhtConfig;
 
 /**
@@ -58,6 +59,8 @@ public:
         bool threaded;
         std::string proxy_server;
         std::string push_node_id;
+        bool peer_discovery;
+        bool peer_publish;
     };
 
     DhtRunner();
@@ -259,6 +262,12 @@ public:
      */
     void bootstrap(const std::string& host, const std::string& service);
 
+    /**
+     * Insert known nodes to the routing table, without necessarly ping them.
+     * Usefull to restart a node and get things running fast without putting load on the network.
+     */
+    void bootstrap(const InfoHash& id, const SockAddr& address);
+
     /**
      * Clear the list of bootstrap added using bootstrap(const std::string&, const std::string&).
      */
@@ -361,7 +370,9 @@ public:
             },
             /*.threaded = */threaded,
             /*.proxy_server = */"",
-            /*.push_node_id = */""
+            /*.push_node_id = */"",
+            /*.peer_discovery = */true,
+            /*.peer_publish = */true,
         });
     }
     void run(in_port_t port, const Config& config);
@@ -526,6 +537,12 @@ private:
 
     /** Push notification token */
     std::string pushToken_;
+
+    /** PeerDiscovery Parameters */
+    std::unique_ptr<PeerDiscovery> peerDiscovery4_;
+    std::unique_ptr<PeerDiscovery> peerDiscovery6_;
+    const in_port_t port_multicast = 8888;
+
 };
 
 }
diff --git a/include/opendht/peer_discovery.h b/include/opendht/peer_discovery.h
new file mode 100644
index 0000000000000000000000000000000000000000..862f9c5fbacd36b125de87cd85238c3ae3731444
--- /dev/null
+++ b/include/opendht/peer_discovery.h
@@ -0,0 +1,144 @@
+/*
+ *  Copyright (C) 2014-2019 Savoir-faire Linux Inc.
+ *  Author(s) : Mingrui Zhang <mingrui.zhang@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, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "sockaddr.h"
+#include "infohash.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h> 
+
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+namespace dht {
+
+class OPENDHT_PUBLIC PeerDiscovery
+{
+public:
+
+    using PeerDiscoveredCallback = std::function<void(const InfoHash&, const SockAddr&)>;
+
+    PeerDiscovery(sa_family_t domain, in_port_t port);
+    ~PeerDiscovery();
+    
+    /**
+     * startDiscovery - Keep Listening data from the sender until node is joinned or stop is called
+    */
+    void startDiscovery(PeerDiscoveredCallback callback);
+
+    /**
+     * startPublish - Keeping sending data until node is joinned or stop is called
+    */
+    void startPublish(const dht::InfoHash &nodeId, in_port_t port_to_send);
+
+    /**
+     * Thread Stopper
+    */
+    void stop();
+
+    /**
+     * Configure the sockopt to be able to listen multicast group
+    */
+    static void socketJoinMulticast(int sockfd, sa_family_t family);
+
+    /**
+     * Join the threads
+    */
+    void join(){
+
+        if(running_listen.joinable()){ running_listen.join(); };
+        if(running_send.joinable()){ running_send.join(); };
+
+    }
+    
+private:
+    std::mutex mtx_;
+    std::condition_variable cv_;
+    bool running_ {true};
+    sa_family_t domain_ {AF_UNSPEC};
+    int port_;
+    int sockfd_ {-1};
+    int stop_writefd_ {-1};
+
+    SockAddr sockAddrSend_;
+    std::array<uint8_t,dht::InfoHash::size() + sizeof(in_port_t)> data_send_;
+
+    //Thread export to be joined 
+    std::thread running_listen;
+    std::thread running_send;
+    dht::InfoHash nodeId_;
+
+    /**
+     * Multicast Socket Initialization, accept IPV4, IPV6 
+    */
+    static int initialize_socket(sa_family_t domain);
+
+    /**
+     * Send messages
+    */
+    void sendTo(uint8_t *buf,size_t buf_size);
+
+    /**
+     * Receive messages
+    */
+    SockAddr recvFrom(uint8_t *buf, size_t &buf_size);
+
+    /**
+     * Send thread loop
+    */
+    void sender_thread();
+
+    /**
+     * Listener thread loop
+    */
+    void listener_thread(PeerDiscoveredCallback callback);
+
+    /**
+     * Listener Parameters Setup
+    */
+    void listener_setup();
+
+    /**
+     * Sender Parameters Setup
+    */
+    void sender_setup(const dht::InfoHash& nodeId, in_port_t port_to_send);
+
+    /**
+     * Binary Converters
+    */
+    static void inttolitend(uint32_t x, uint8_t *lit_int) {
+        lit_int[0] = (uint8_t)(x >>  0);
+        lit_int[1] = (uint8_t)(x >>  8);
+    }
+
+    static uint16_t litendtoint(uint8_t *lit_int) {
+        return (uint32_t)lit_int[0] <<  0
+            |  (uint32_t)lit_int[1] <<  8;
+    }
+
+#ifdef _WIN32
+    WSADATA wsaData;
+#endif
+
+};
+
+}
diff --git a/include/opendht/sockaddr.h b/include/opendht/sockaddr.h
index 7cfa5575bcfd5a722f0dcd67dccfc5584b284f6c..684c4a03d03b913dc991f91af3cdd9cbd9110450 100644
--- a/include/opendht/sockaddr.h
+++ b/include/opendht/sockaddr.h
@@ -23,6 +23,7 @@
 #ifndef _WIN32
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <arpa/inet.h>
 #ifdef __ANDROID__
 typedef uint16_t in_port_t;
 #endif
@@ -90,6 +91,8 @@ public:
 
     static std::vector<SockAddr> resolve(const std::string& host, const std::string& service = {});
 
+    static SockAddr parse(sa_family_t family, const char* address);
+
     bool operator<(const SockAddr& o) const {
         if (len != o.len)
             return len < o.len;
@@ -146,6 +149,21 @@ public:
             addr->sa_family = af;
     }
 
+    /**
+     * Set Network Interface to any
+     */
+    void setAny() {
+        auto family = getFamily();
+        switch(family) {
+        case AF_INET:
+            getIPv4().sin_addr.s_addr = htonl(INADDR_ANY);
+            break;
+        case AF_INET6:
+            getIPv6().sin6_addr = in6addr_any;
+            break;
+        }
+    }
+
     /**
      * Retreive the port (in host byte order) or 0 if the address is not
      * of a supported family.
diff --git a/src/dhtrunner.cpp b/src/dhtrunner.cpp
index 8d24555aee72885eb2586616c74e5aa7c60bce7e..ae68fdf53b1503d07c1908ddaab5907a71277b69 100644
--- a/src/dhtrunner.cpp
+++ b/src/dhtrunner.cpp
@@ -20,6 +20,7 @@
 
 #include "dhtrunner.h"
 #include "securedht.h"
+#include "peer_discovery.h"
 
 #ifdef OPENDHT_PROXY_CLIENT
 #include "dht_proxy_client.h"
@@ -147,6 +148,36 @@ DhtRunner::run(const SockAddr& local4, const SockAddr& local6, const DhtRunner::
                 cv.wait_until(lk, wakeup, hasJobToDo);
         }
     });
+
+    if (config.peer_discovery or config.peer_publish) {
+        peerDiscovery4_.reset(new PeerDiscovery(AF_INET, port_multicast));
+        peerDiscovery6_.reset(new PeerDiscovery(AF_INET6, port_multicast));
+    }
+    if (config.peer_discovery) {
+        using sig = void (DhtRunner::*)(const InfoHash&, const SockAddr&);
+        peerDiscovery4_->startDiscovery(std::bind(static_cast<sig>(&DhtRunner::bootstrap),
+                                                   this,
+                                                   std::placeholders::_1,std::placeholders::_2));
+        peerDiscovery6_->startDiscovery(std::bind(static_cast<sig>(&DhtRunner::bootstrap),
+                                                   this,
+                                                   std::placeholders::_1,std::placeholders::_2));
+    }
+    if (config.peer_publish) {
+        peerDiscovery4_->startPublish(getNodeId(),getBoundPort());
+        peerDiscovery6_->startPublish(getNodeId(),getBoundPort());
+    }
+}
+
+void DhtRunner::bootstrap(const InfoHash& id, const SockAddr& address)
+{
+    {
+        std::unique_lock<std::mutex> lck(storage_mtx);
+        pending_ops_prio.emplace([id, address](SecureDht& dht) mutable {
+            std::cout<<address.getPort()<<std::endl;
+            dht.insertNode(id, address);
+        });
+    }
+    cv.notify_all();
 }
 
 void
@@ -169,12 +200,21 @@ DhtRunner::join()
     running = false;
     cv.notify_all();
     bootstrap_cv.notify_all();
+    if (peerDiscovery4_)
+        peerDiscovery4_->stop();
+    if (peerDiscovery6_)
+        peerDiscovery6_->stop();
+
     if (dht_thread.joinable())
         dht_thread.join();
     if (bootstrap_thread.joinable())
         bootstrap_thread.join();
     if (rcv_thread.joinable())
         rcv_thread.join();
+    if (peerDiscovery4_)
+        peerDiscovery4_->join();
+    if (peerDiscovery6_)
+        peerDiscovery6_->join();
 
     {
         std::lock_guard<std::mutex> lck(storage_mtx);
@@ -592,7 +632,8 @@ DhtRunner::startNetwork(const SockAddr sin4, const SockAddr sin6)
                         rc = recvfrom(s6, (char*)buf.data(), buf.size(), 0, (sockaddr*)&from, &from_len);
                     else
                         continue;
-                    if (rc > 0) {
+
+                    if (rc > 0 && rc != static_cast<int>(getNodeId().size() + 2)) {
                         {
                             std::lock_guard<std::mutex> lck(sock_mtx);
                             if (rcv.size() >= RX_QUEUE_MAX_SIZE) {
@@ -974,6 +1015,8 @@ DhtRunner::findCertificate(InfoHash hash, std::function<void(const std::shared_p
 void
 DhtRunner::resetDht()
 {
+    peerDiscovery4_.reset();
+    peerDiscovery6_.reset();
 #ifdef OPENDHT_PROXY_CLIENT
     listeners_.clear();
     dht_via_proxy_.reset();
diff --git a/src/peer_discovery.cpp b/src/peer_discovery.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..972f265dfd3d857fbc08d49f1cbc17e94a48ffa8
--- /dev/null
+++ b/src/peer_discovery.cpp
@@ -0,0 +1,350 @@
+/*
+ *  Copyright (C) 2014-2019 Savoir-faire Linux Inc.
+ *  Author(s) : Mingrui Zhang <mingrui.zhang@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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "peer_discovery.h"
+
+#ifdef _WIN32
+#include <Ws2tcpip.h> // needed for ip_mreq definition for multicast
+#include <Windows.h>
+#else
+#include <sys/types.h>
+#endif
+#include <fcntl.h>
+
+namespace dht {
+
+constexpr char MULTICAST_ADDRESS_IPV4[10] = "224.0.0.1";
+constexpr char MULTICAST_ADDRESS_IPV6[8] = "ff02::1";
+
+#ifdef _WIN32
+
+static bool
+set_nonblocking(int fd, int nonblocking)
+{
+    unsigned long mode = !!nonblocking;
+    int rc = ioctlsocket(fd, FIONBIO, &mode);
+    return rc == 0;
+}
+
+extern const char *inet_ntop(int, const void *, char *, socklen_t);
+
+#else
+
+static bool
+set_nonblocking(int fd, int nonblocking)
+{
+    int rc = fcntl(fd, F_GETFL, 0);
+    if (rc < 0)
+        return false;
+    rc = fcntl(fd, F_SETFL, nonblocking?(rc | O_NONBLOCK):(rc & ~O_NONBLOCK));
+    return rc >= 0;
+}
+
+#endif
+
+PeerDiscovery::PeerDiscovery(sa_family_t domain, in_port_t port)
+    : domain_(domain), port_(port), sockfd_(initialize_socket(domain))
+{
+    socketJoinMulticast(sockfd_, domain);
+}
+
+int
+PeerDiscovery::initialize_socket(sa_family_t domain)
+{
+
+#ifdef _WIN32
+    // Initialize Windows Socket API with given VERSION.
+    if (WSAStartup(0x0101, &wsaData)) {
+        perror("WSAStartup");
+        throw std::runtime_error(std::string("Socket Creation Error_initialize_socket ") + strerror(errno));
+    }
+#endif
+
+    int sockfd = socket(domain, SOCK_DGRAM, 0);
+    if (sockfd < 0) {
+        throw std::runtime_error(std::string("Socket Creation Error: ") + strerror(errno));
+    }
+    set_nonblocking(sockfd, 1);
+    return sockfd;
+}
+
+void
+PeerDiscovery::listener_setup()
+{   
+    SockAddr sockAddrListen_;
+    sockAddrListen_.setFamily(domain_);
+    sockAddrListen_.setPort(port_);
+    sockAddrListen_.setAny();
+
+    unsigned int opt = 1;
+    if (setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, (char*) &opt, sizeof(opt)) < 0){
+       throw std::runtime_error(std::string("Reusing ADDR failed: ") + strerror(errno));
+    }
+
+    // bind to receive address
+    if (bind(sockfd_, sockAddrListen_.get(), sockAddrListen_.getLength()) < 0){
+        throw std::runtime_error(std::string("Bind Socket For Listener Error: ") + strerror(errno));
+    }
+}
+
+void
+PeerDiscovery::socketJoinMulticast(int sockfd, sa_family_t family)
+{
+    switch (family)
+    {
+        case AF_INET:{
+
+            ip_mreq config_ipv4;
+            
+            //This option can be used to set the interface for sending outbound 
+            //multicast datagrams from the sockets application.
+            config_ipv4.imr_interface.s_addr = htonl(INADDR_ANY);
+            if( setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &config_ipv4.imr_interface, sizeof( struct in_addr )) < 0 ) {
+                throw std::runtime_error(std::string("Bound Network Interface IPV4 Error: ") + strerror(errno));
+            }
+
+            //The IP_MULTICAST_TTL socket option allows the application to primarily 
+            //limit the lifetime of the packet in the Internet and prevent it from circulating indefinitely
+            unsigned char ttl4 = 20;
+            if( setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl4, sizeof( ttl4 )) < 0 ) {
+                throw std::runtime_error(std::string(" TTL Sockopt Error: ") + strerror(errno));
+            }
+            
+            // config the listener to be interested in joining in the multicast group
+            config_ipv4.imr_multiaddr.s_addr = inet_addr(MULTICAST_ADDRESS_IPV4);
+            config_ipv4.imr_interface.s_addr = htonl(INADDR_ANY);
+            if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&config_ipv4, sizeof(config_ipv4)) < 0){
+                throw std::runtime_error(std::string(" Member Addition IPV4 Error: ") + strerror(errno));
+            }
+
+            break;
+        }
+    
+        case AF_INET6:{
+
+            ipv6_mreq config_ipv6;
+
+            unsigned int outif = 0;
+            if( setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &outif, sizeof( outif )) < 0 ) {
+                throw std::runtime_error(std::string("Bound Network Interface IPV6 Error: ") + strerror(errno));
+            }
+
+            unsigned int ttl6 = 20;
+            if( setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl6, sizeof( ttl6 )) < 0 ) {
+                throw std::runtime_error(std::string("Hop Count Set Error: ") + strerror(errno));
+            }
+
+            config_ipv6.ipv6mr_interface = 0;
+            inet_pton(AF_INET6, MULTICAST_ADDRESS_IPV6, &config_ipv6.ipv6mr_multiaddr);
+            if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &config_ipv6, sizeof(config_ipv6)) < 0){
+                throw std::runtime_error(std::string("Member Addition IPV6 Error: ") + strerror(errno));
+            }
+
+            break;
+        }
+
+    }       
+}
+
+void
+PeerDiscovery::sendTo(uint8_t *buf, size_t buf_size)
+{
+    ssize_t nbytes = sendto(
+        sockfd_,
+        buf,
+        buf_size,
+        0,
+        sockAddrSend_.get(),
+        sockAddrSend_.getLength()
+    );
+    if (nbytes < 0) {
+        throw std::runtime_error(std::string("Error sending packet: ") + strerror(errno));
+    }
+}
+
+SockAddr
+PeerDiscovery::recvFrom(uint8_t *buf, size_t& buf_size)
+{
+    sockaddr_storage storeage_recv;
+    socklen_t sa_len = sizeof(storeage_recv);
+
+    ssize_t nbytes = recvfrom(
+        sockfd_,
+        buf,
+        buf_size,
+        0,
+        (sockaddr*)&storeage_recv,
+        &sa_len
+    );
+    if (nbytes < 0) {
+        throw std::runtime_error(std::string("Error receiving packet: ") + strerror(errno));
+    }
+
+    buf_size = nbytes;
+    SockAddr ret {storeage_recv, sa_len};
+    return ret;
+}
+
+void
+PeerDiscovery::sender_setup(const dht::InfoHash& nodeId, in_port_t port_to_send)
+{
+    nodeId_ = nodeId;
+    //Set up for Sender
+    sockAddrSend_ = SockAddr::parse(domain_, domain_ == AF_INET ? MULTICAST_ADDRESS_IPV4 : MULTICAST_ADDRESS_IPV6);
+    sockAddrSend_.setPort(port_);
+
+    //Setup for send data
+    int port_node = port_to_send;
+    uint8_t port_node_binary[2];
+    PeerDiscovery::inttolitend(port_node,port_node_binary);
+
+    //Copy Node id and node port
+    memcpy (data_send_.data(), nodeId.data(), nodeId.size());
+    data_send_[InfoHash::size()] = port_node_binary[0];
+    data_send_[InfoHash::size() + 1] = port_node_binary[1];
+}
+
+void
+PeerDiscovery::sender_thread()
+{
+    while(true) {
+        sendTo(data_send_.data(), data_send_.size());
+        {
+            std::unique_lock<std::mutex> lck(mtx_);
+            if (cv_.wait_for(lck,std::chrono::seconds(3),[&]{ return !running_; }))
+                break;
+        }
+    }
+}
+
+void
+PeerDiscovery::listener_thread(PeerDiscoveredCallback callback)
+{
+    int stopfds_pipe[2];
+#ifndef _WIN32
+    auto status = pipe(stopfds_pipe);
+    if (status == -1) {
+        throw std::runtime_error(std::string("Can't open pipe: ") + strerror(errno));
+    }
+#else
+    udpPipe(stopfds_pipe);
+#endif
+    int stop_readfd = stopfds_pipe[0];
+    stop_writefd_ = stopfds_pipe[1];
+
+    while(true) {
+        fd_set readfds;
+
+        FD_ZERO(&readfds);
+        FD_SET(stop_readfd, &readfds);
+        FD_SET(sockfd_, &readfds);
+
+        int data_coming = select(sockfd_ > stop_readfd ? sockfd_ + 1 : stop_readfd + 1, &readfds, nullptr, nullptr, nullptr);
+
+        {
+            std::unique_lock<std::mutex> lck(mtx_);
+            if (not running_)
+                break;
+        }
+
+        if (data_coming < 0) {
+            if(errno != EINTR) {
+                perror("Select Error");
+                std::this_thread::sleep_for( std::chrono::seconds(1) );
+            }
+        }
+
+        if (data_coming > 0) {
+
+            if(FD_ISSET(stop_readfd, &readfds)){ break; }
+
+            std::array<uint8_t,dht::InfoHash::size() + sizeof(in_port_t)> data_receive;
+            size_t data_receive_size = data_receive.size();
+            auto from = recvFrom(data_receive.data(), data_receive_size);
+
+            //Data_receive_size as a value-result member will hlep to filter packs 
+            if(data_receive_size != data_receive.size()){ 
+                perror("Data Received Unmatch");
+                continue; 
+            }
+
+            std::array<uint8_t,dht::InfoHash::size()> data_infohash;
+            uint8_t data_port[2];
+
+            memcpy (data_infohash.data(), data_receive.data(), dht::InfoHash::size());
+            data_port[0] = data_receive[dht::InfoHash::size()];
+            data_port[1] = data_receive[dht::InfoHash::size() + 1];
+
+            auto port = PeerDiscovery::litendtoint(data_port);
+            auto nodeId = dht::InfoHash(data_infohash.data(), dht::InfoHash::size());
+
+            if (nodeId != nodeId_){
+                from.setPort(port);
+                callback(nodeId, from);
+            }
+        }
+    }
+    if (stop_readfd != -1)
+        close(stop_readfd);
+    if (stop_writefd_ != -1) {
+        close(stop_writefd_);
+        stop_writefd_ = -1;
+    }
+}
+
+void
+PeerDiscovery::startDiscovery(PeerDiscoveredCallback callback)
+{
+    listener_setup();
+    running_listen = std::thread(&PeerDiscovery::listener_thread, this, callback);
+}
+
+void
+PeerDiscovery::startPublish(const dht::InfoHash &nodeId, in_port_t port_to_send)
+{
+    sender_setup(nodeId, port_to_send);
+    running_send = std::thread(&PeerDiscovery::sender_thread, this);
+}
+
+void
+PeerDiscovery::stop()
+{
+    {
+        std::unique_lock<std::mutex> lck(mtx_);
+        running_ = false;
+    }
+    cv_.notify_one();
+    if (stop_writefd_ != -1) {
+
+        if (write(stop_writefd_, "\0", 1) == -1) {
+            perror("write");
+        }
+    }
+}
+
+PeerDiscovery::~PeerDiscovery()
+{
+    if (sockfd_ != -1)
+        close(sockfd_);
+
+#ifdef _WIN32
+    WSACleanup();
+#endif
+}
+
+}
diff --git a/src/utils.cpp b/src/utils.cpp
index 0b75ac48d28e3a834118f15cc0012e37af6666b8..d5c55049ba88ab831232b09353e55d911cfb3501 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -74,6 +74,18 @@ SockAddr::resolve(const std::string& host, const std::string& service)
 }
 
 
+SockAddr
+SockAddr::parse(sa_family_t family, const char* address)
+{
+    SockAddr addr;
+    addr.setFamily(family);
+    if (inet_pton(family, address, family == AF_INET ? (void*)&addr.getIPv4().sin_addr.s_addr : (void*)&addr.getIPv6().sin6_addr) <= 0){
+        throw std::runtime_error("SockAddr::parse inet_pton");
+    }
+    return addr;
+}
+
+
 std::string
 print_addr(const sockaddr* sa, socklen_t slen)
 {
diff --git a/tests/peerdiscoverytester.cpp b/tests/peerdiscoverytester.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..42e4ad28a183fb396ea8712103244df5c91dcfba
--- /dev/null
+++ b/tests/peerdiscoverytester.cpp
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *
+ *  Author: Mingrui Zhang <mingrui.zhang@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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "peerdiscoverytester.h"
+
+namespace test {
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PeerDiscoveryTester);
+
+void PeerDiscoveryTester::setUp(){}
+
+void PeerDiscoveryTester::testTransmission_ipv4(){
+
+    // Node for getnode id
+    dht::InfoHash data_n = dht::InfoHash::get("applepin");
+    int port = 2222;
+    in_port_t port_n = 50000;;
+
+    dht::PeerDiscovery test_n(AF_INET, port);
+    dht::PeerDiscovery test_s(AF_INET, port);
+
+    test_s.startDiscovery([&](const dht::InfoHash& node, const dht::SockAddr& addr){
+        CPPUNIT_ASSERT_EQUAL(data_n, node);
+        CPPUNIT_ASSERT_EQUAL(port_n, addr.getPort());
+    });
+
+    test_n.startPublish(data_n, port_n);
+
+    sleep(5);
+    test_n.stop();
+    test_s.stop();
+    test_n.join();
+    test_s.join();
+}
+
+/*void PeerDiscoveryTester::testTransmission_ipv6(){
+
+    // Node for getnode id
+    dht::InfoHash data_n = dht::InfoHash::get("applepin");
+    int port = 2222;
+    int port_n = 50000;;
+
+    dht::PeerDiscovery test_n(AF_INET6,port);
+    dht::PeerDiscovery test_s(AF_INET6,port);
+
+    test_s.startDiscovery([&](const dht::InfoHash& node, const dht::SockAddr& addr){
+
+        CPPUNIT_ASSERT_MESSAGE("Data Receive Incorrect", memcmp(node.data(),data_n.data(),dht::InfoHash::size()) == 0 );
+        CPPUNIT_ASSERT_MESSAGE("Port Receive Incorrect", addr.getPort() == 50000);
+
+    });
+
+    test_n.startPublish(data_n,port_n);
+
+    sleep(5);
+    test_n.stop();
+    test_s.stop();
+    test_n.join();
+    test_s.join();
+
+}*/
+
+void PeerDiscoveryTester::tearDown(){}
+
+}  // namespace test
\ No newline at end of file
diff --git a/tests/peerdiscoverytester.h b/tests/peerdiscoverytester.h
new file mode 100644
index 0000000000000000000000000000000000000000..3d5438cc453cb16c58f258116297143c8c6ed9bd
--- /dev/null
+++ b/tests/peerdiscoverytester.h
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *
+ *  Author: Mingrui Zhang <mingrui.zhang@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, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "opendht/peer_discovery.h"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace test {
+
+class PeerDiscoveryTester : public CppUnit::TestFixture {
+
+    CPPUNIT_TEST_SUITE(PeerDiscoveryTester);
+    CPPUNIT_TEST(testTransmission_ipv4);
+   // CPPUNIT_TEST(testTransmission_ipv6);
+    CPPUNIT_TEST_SUITE_END();
+
+ public:
+    /**
+     * Method automatically called before each test by CppUnit
+     */
+    void setUp();
+    /**
+     * Method automatically called after each test CppUnit
+     */
+    void tearDown();
+    /**
+     * Test Multicast Transmission Ipv4
+     */
+    void testTransmission_ipv4();
+    /**
+     * Test Multicast Transmission Ipv6
+     */
+    //void testTransmission_ipv6();
+
+};
+
+}  // namespace test