diff --git a/src/upnp/protocol/natpmp/nat_pmp.cpp b/src/upnp/protocol/natpmp/nat_pmp.cpp
index 30a339195b2c36302b5c30900596839a339153d8..42aad9a9b6bf56566522df7b1ab59c359c3f968e 100644
--- a/src/upnp/protocol/natpmp/nat_pmp.cpp
+++ b/src/upnp/protocol/natpmp/nat_pmp.cpp
@@ -97,7 +97,7 @@ NatPmp::~NatPmp()
 }
 
 void
-NatPmp::clearIGDs()
+NatPmp::clearIgds()
 {
     // Lock valid IGD.
     std::lock_guard<std::mutex> lock(validIgdMutex_);
@@ -113,7 +113,7 @@ NatPmp::clearIGDs()
 }
 
 void
-NatPmp::searchForIGD()
+NatPmp::searchForIgd()
 {
     // Lock valid IGD.
     std::lock_guard<std::mutex> lock(validIgdMutex_);
diff --git a/src/upnp/protocol/natpmp/nat_pmp.h b/src/upnp/protocol/natpmp/nat_pmp.h
index f463c9fa6240832620defeacc934bfef426c22a1..ab3b409d881a26ea6d38df007d7d664078cbdabc 100644
--- a/src/upnp/protocol/natpmp/nat_pmp.h
+++ b/src/upnp/protocol/natpmp/nat_pmp.h
@@ -55,10 +55,10 @@ public:
     Type getType() const override { return Type::NAT_PMP; }
 
     // Notifies a change in network.
-    void clearIGDs() override;
+    void clearIgds() override;
 
     // Renew pmp_igd.
-    void searchForIGD() override;
+    void searchForIgd() override;
 
     // Tries to add mapping. Assumes mutex is already locked.
     Mapping addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, PortType type, UPnPProtocol::UpnpError& upnp_error) override;
diff --git a/src/upnp/protocol/pupnp/pupnp.cpp b/src/upnp/protocol/pupnp/pupnp.cpp
index 0b4b00659d3cdbd91d4f95327724695c3e9c9a93..59490b16c9ce84f5c672b2fda0b9d5c687cdb731 100644
--- a/src/upnp/protocol/pupnp/pupnp.cpp
+++ b/src/upnp/protocol/pupnp/pupnp.cpp
@@ -2,7 +2,8 @@
  *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
  *
  *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
- *	Author: Eden Abitbol <eden.abitbol@savoirfairelinux.com>
+ *  Author: Eden Abitbol <eden.abitbol@savoirfairelinux.com>
+ *  Author: 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
@@ -21,6 +22,8 @@
 
 #include "pupnp.h"
 
+#include <opendht/thread_pool.h>
+
 namespace jami { namespace upnp {
 
 // Helper functions for xml parsing.
@@ -80,7 +83,64 @@ errorOnResponse(IXML_Document* doc)
 
 PUPnP::PUPnP()
 {
-    pupnpThread_ = std::thread([this] { registerClientAsync(); });
+    pupnpThread_ = std::thread([this] {
+        std::unique_lock<std::mutex> lk(ctrlptMutex_);
+        while (pupnpRun_) {
+            pupnpCv_.wait(lk);
+
+            if (not clientRegistered_) {
+                // Register Upnp control point.
+                int upnp_err = UpnpRegisterClient(ctrlPtCallback, this, &ctrlptHandle_);
+                if (upnp_err != UPNP_E_SUCCESS)
+                    JAMI_ERR("PUPnP: Can't register client: %s", UpnpGetErrorMessage(upnp_err));
+                else
+                    clientRegistered_ = true;
+            }
+
+            if (not pupnpRun_)
+                break;
+
+            if (clientRegistered_ and searchForIgd_) {
+                // Send out search for multiple types of devices, as some routers may possibly only reply to one.
+                UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this);
+                UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this);
+                UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this);
+                UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this);
+
+                // Reset variable.
+                searchForIgd_ = false;
+            }
+
+            if (clientRegistered_) {
+                auto xmlList = std::move(dwnldlXmlList_);
+                decltype(xmlList) finished {};
+
+                // Wait on futures asynchronously
+                lk.unlock();
+                for (auto it = xmlList.begin(); it != xmlList.end();) {
+                    if (it->wait_for(std::chrono::seconds(1)) == std::future_status::ready) {
+                        finished.splice(finished.end(), xmlList, it++);
+                    } else {
+                        JAMI_WARN("PUPnP: XML download timed out");
+                        ++it;
+                    }
+                }
+                lk.lock();
+
+                // Move back failed items to end of list
+                dwnldlXmlList_.splice(dwnldlXmlList_.end(), xmlList);
+                // Handle successful downloads
+                for (auto& item : finished) {
+                    auto result = item.get();
+                    if (not result.document or not validateIgd(result)) {
+                        cpDeviceList_.erase(result.location);
+                    }
+                }
+            }
+        }
+
+        UpnpFinish();
+    });
 
     int upnp_err = UPNP_E_SUCCESS;
     char* ip_address = nullptr;
@@ -109,11 +169,10 @@ PUPnP::PUPnP()
         ip_address6 = UpnpGetServerIp6Address();
         port6 = UpnpGetServerPort6();
 #endif
-        if (ip_address6 and port6) {
+        if (ip_address6 and port6)
             JAMI_DBG("PUPnP: Initialiazed on %s:%u | %s:%u", ip_address, port, ip_address6, port6);
-        } else {
+        else
             JAMI_DBG("PUPnP: Initialiazed on %s:%u", ip_address, port);
-        }
 
         // Relax the parser to allow malformed XML text.
         ixmlRelaxParser(1);
@@ -122,47 +181,133 @@ PUPnP::PUPnP()
 
 PUPnP::~PUPnP()
 {
+    pupnpCv_.notify_all();
     // Clear all the lists.
     {
         std::lock_guard<std::mutex> lk(validIgdMutex_);
         for(auto const &it : validIgdList_) {
-            if (auto igd = dynamic_cast<UPnPIGD*>(it.second.get()))
+            if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it.second))
                 actionDeletePortMappingsByDesc(*igd, Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION);
         }
         validIgdList_.clear();
         cpDeviceList_.clear();
+        dwnldlXmlList_.clear();
     }
 
     // Notify thread to terminate. UpnpFinish function will get called.
     pupnpRun_ = false;
     pupnpCv_.notify_all();
-    if (pupnpThread_.joinable()) {
+    if (pupnpThread_.joinable())
         pupnpThread_.join();
-    }
 }
 
 void
-PUPnP::clearIGDs()
+PUPnP::clearIgds()
 {
     // Lock internal IGD list.
     std::lock_guard<std::mutex> lk(validIgdMutex_);
 
-    // Clear internal IGD list.
+    // Clear all internal lists.
+    dwnldlXmlList_.clear();
     validIgdList_.clear();
     cpDeviceList_.clear();
 }
 
 void
-PUPnP::searchForIGD()
+PUPnP::searchForIgd()
 {
-    // Notify registerClientAsync function running in thread to execute in non-blocking fashion.
+    // Notify thread to execute in non-blocking fashion.
     {
         std::lock_guard<std::mutex> lk(ctrlptMutex_);
-        pupnpRun_ = true;
+        searchForIgd_ = true;
     }
     pupnpCv_.notify_one();
 }
 
+bool
+PUPnP::validateIgd(const IGDInfo& info)
+{
+    auto descDoc = info.document.get();
+    // Check device type.
+    std::string deviceType = getFirstDocItem(descDoc, "deviceType");
+    if (deviceType.empty()) {
+        // No device type.
+        return false;
+    }
+
+    if (deviceType.compare(UPNP_IGD_DEVICE) != 0) {
+        // Device type not IGD.
+        return false;
+    }
+
+    auto igd_candidate = parseIgd(descDoc, info.location);
+    if (not igd_candidate) {
+        // No valid IGD candidate.
+        return false;
+    }
+
+    JAMI_DBG("PUPnP: Validating IGD candidate\n"
+             "    UDN: %s\n"
+             "    Base URL: %s\n"
+             "    Name: %s\n"
+             "    serviceType: %s\n"
+             "    serviceID: %s\n"
+             "    locationURL: %s\n"
+             "    controlURL: %s\n"
+             "    eventSubURL: %s",
+             igd_candidate->getUDN().c_str(),
+             igd_candidate->getBaseURL().c_str(),
+             igd_candidate->getFriendlyName().c_str(),
+             igd_candidate->getServiceType().c_str(),
+             igd_candidate->getServiceId().c_str(),
+             igd_candidate->getLocationURL().c_str(),
+             igd_candidate->getControlURL().c_str(),
+             igd_candidate->getEventSubURL().c_str());
+
+    // Check if IGD is connected.
+    if (not actionIsIgdConnected(*igd_candidate)) {
+        JAMI_WARN("PUPnP: IGD candidate %s is not connected", igd_candidate->getUDN().c_str());
+        return false;
+    }
+
+    // Validate external Ip.
+    igd_candidate->publicIp_ = actionGetExternalIP(*igd_candidate);
+    if (igd_candidate->publicIp_.toString().empty()) {
+        JAMI_WARN("PUPnP: IGD candidate %s has no valid external Ip", igd_candidate->getUDN().c_str());
+        return false;
+    }
+
+    // Validate internal Ip.
+    igd_candidate->localIp_ = ip_utils::getLocalAddr(pj_AF_INET());
+    if (igd_candidate->localIp_.toString().empty()) {
+        JAMI_WARN("PUPnP: No valid internal Ip.");
+        return false;
+    }
+
+    JAMI_DBG("PUPnP: Found device with external IP %s", igd_candidate->publicIp_.toString().c_str());
+
+    // Store info for subscription.
+    std::string eventSub = igd_candidate->getEventSubURL();
+    std::string udn = igd_candidate->getUDN();
+
+    // Remove any local mappings that may be left over from last time used.
+    removeAllLocalMappings(igd_candidate.get());
+
+    // Add the igd to the upnp context class list.
+    if (updateIgdListCb_(this, std::move(igd_candidate.get()), std::move(igd_candidate.get()->publicIp_), true))
+        JAMI_DBG("PUPnP: IGD with public IP %s was added to the list", igd_candidate->publicIp_.toString().c_str());
+
+    // Keep local IGD list internally.
+    validIgdList_.emplace(igd_candidate->getUDN(), std::move(igd_candidate)).first;
+
+    // Subscribe to IGD events.
+    int upnp_err = UpnpSubscribeAsync(ctrlptHandle_, eventSub.c_str(), SUBSCRIBE_TIMEOUT, subEventCallback, this);
+    if (upnp_err != UPNP_E_SUCCESS)
+        JAMI_WARN("PUPnP: Error when trying to request subscription for %s -> %s", udn.c_str(), UpnpGetErrorMessage(upnp_err));
+
+    return true;
+}
+
 Mapping
 PUPnP::addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, PortType type, UPnPProtocol::UpnpError& upnp_error)
 {
@@ -179,17 +324,17 @@ PUPnP::addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, Port
     auto globalMappings = type == PortType::UDP ? &igd->udpMappings : &igd->tcpMappings;
     auto iter = globalMappings->find(port_external);
     if (iter != globalMappings->end()) {
-        /* mapping exists with same external port */
+        // Mapping exists with same external port.
         GlobalMapping* mapping_ptr = &iter->second;
         if (*mapping_ptr == mapping) {
-            /* the same mapping, so nothing needs to be done */
+            // The same mapping, so nothing needs to be done.
             upnp_error = UPnPProtocol::UpnpError::ERROR_OK;
             ++(mapping_ptr->users);
             JAMI_DBG("PUPnP: Mapping already exists, incrementing number of users: %d",
                      iter->second.users);
             return mapping;
         } else {
-            /* this port is already used by a different mapping */
+            // This port is already used by a different mapping.
             JAMI_WARN("PUPnP: Cannot add a mapping with an external port which is already used by another:\n\tcurrent: %s\n\ttrying to add: %s",
                       mapping_ptr->toString().c_str(), mapping.toString().c_str());
             upnp_error = UPnPProtocol::UpnpError::CONFLICT_IN_MAPPING;
@@ -197,7 +342,7 @@ PUPnP::addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, Port
         }
     }
 
-    if (auto pupnp_igd = dynamic_cast<const UPnPIGD*>(igd)) { 
+    if (auto pupnp_igd = dynamic_cast<const UPnPIGD*>(igd)) {
         if (actionAddPortMapping(*pupnp_igd, mapping, upnp_error)) {
             JAMI_WARN("PUPnP: Opened port %s", mapping.toString().c_str());
             globalMappings->emplace(port_external, GlobalMapping{mapping});
@@ -216,17 +361,15 @@ PUPnP::removeMapping(const Mapping& igdMapping)
     // Iterate over all IGDs in internal list and try to remove selected mapping.
     for (auto const& item : validIgdList_) {
 
-        if (not item.second) {
+        if (not item.second)
             continue;
-        }
 
         // Get mappings of IGD depending on type.
         PortMapGlobal* globalMappings;
-        if (igdMapping.getType() == PortType::UDP) {
+        if (igdMapping.getType() == PortType::UDP)
             globalMappings = &item.second->udpMappings;
-        } else {
+        else
             globalMappings = &item.second->tcpMappings;
-        }
 
         // Check if the mapping we want to remove is present in the IGD.
         auto mapToRemove = globalMappings->find(igdMapping.getPortExternal());
@@ -258,52 +401,16 @@ PUPnP::removeMapping(const Mapping& igdMapping)
 void
 PUPnP::removeAllLocalMappings(IGD* igd)
 {
-    if (auto igd_del_map = dynamic_cast<UPnPIGD*>(igd)) {
+    if (auto igd_del_map = dynamic_cast<UPnPIGD*>(igd))
         actionDeletePortMappingsByDesc(*igd_del_map, Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION);
-    }
-}
-
-void
-PUPnP::registerClientAsync()
-{
-    std::unique_lock<std::mutex> lk(ctrlptMutex_);
-
-    while (pupnpRun_) {
-
-        pupnpCv_.wait(lk);
-
-        if (not clientRegistered_) {
-            // Register Upnp control point.
-            int upnp_err = UpnpRegisterClient(ctrlPtCallback, this, &ctrlptHandle_);
-            if (upnp_err != UPNP_E_SUCCESS) {
-                JAMI_ERR("PUPnP: Can't register client: %s", UpnpGetErrorMessage(upnp_err));
-            } else {
-                clientRegistered_ = true;
-            }
-        }
-
-        if (not pupnpRun_) {
-            break;
-        }
-
-        if (clientRegistered_) {
-            // Send out search for multiple types of devices, as some routers may possibly only reply to one.
-            UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this);
-            UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this);
-            UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this);
-            UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this);
-        }
-    }
-
-    UpnpFinish();
 }
 
 int
 PUPnP::ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data)
 {
-    if (auto pupnp = static_cast<PUPnP*>(user_data)) {
+    if (auto pupnp = static_cast<PUPnP*>(user_data))
         return pupnp->handleCtrlPtUPnPEvents(event_type, event);
-    }
+
     JAMI_WARN("PUPnP: Control point callback without PUPnP");
     return 0;
 }
@@ -320,122 +427,38 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
     case UPNP_DISCOVERY_SEARCH_RESULT:
     {
         const UpnpDiscovery* d_event = (const UpnpDiscovery*)event;
-        int upnp_err;
 
         // First check the error code.
-        if (UpnpDiscovery_get_ErrCode(d_event) != UPNP_E_SUCCESS) {
+        if (UpnpDiscovery_get_ErrCode(d_event) != UPNP_E_SUCCESS)
             break;
-        }
 
         // Check if this device ID is already in the list.
         std::string cpDeviceId = UpnpDiscovery_get_DeviceID_cstr(d_event);
         std::lock_guard<std::mutex> lk(validIgdMutex_);
-        if (cpDeviceList_.count(cpDeviceId) > 0) {
+        if (not cpDeviceList_.emplace(cpDeviceId).second)
             break;
-        }
-        cpDeviceList_.emplace(std::move(cpDeviceId), std::string{});
-
-        /*
-         * NOTE: This thing will block until success for the system socket timeout
-         * unless libupnp is compile with '-disable-blocking-tcp-connections', in
-         * which case it will block for the libupnp specified timeout.
-         */
-        IXML_Document* doc_container_ptr = nullptr;
-        std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> doc_desc_ptr(nullptr, ixmlDocument_free);
-        upnp_err = UpnpDownloadXmlDoc(UpnpDiscovery_get_Location_cstr(d_event), &doc_container_ptr);
-        if (doc_container_ptr) {
-            doc_desc_ptr.reset(doc_container_ptr);
-        }
-
-        if (upnp_err != UPNP_E_SUCCESS or not doc_desc_ptr) {
-            JAMI_WARN("PUPnP: Error downloading device XML document -> %s", UpnpGetErrorMessage(upnp_err));
-            break;
-        }
-
-        // Check device type.
-        std::string deviceType = getFirstDocItem(doc_desc_ptr.get(), "deviceType");
-        if (deviceType.empty()) {
-            // No device type. Exit.
-            break;
-        }
-
-        if (deviceType.compare(UPNP_IGD_DEVICE) != 0) {
-            // Device type not IGD. Exit.
-            break;
-        }
-
-        std::unique_ptr<UPnPIGD> igd_candidate;
-        igd_candidate = parseIGD(doc_desc_ptr.get(), d_event);
-        if (not igd_candidate) {
-            // No valid IGD candidate. Exit.
-            break;
-        }
-
-        JAMI_DBG("PUPnP: Validating IGD candidate.\n\tUDN: %s\n\tBase URL: %s\n\tName: %s\n\tserviceType: %s\n\tserviceID: %s\n\tcontrolURL: %s\n\teventSubURL: %s",
-                    igd_candidate->getUDN().c_str(),
-                    igd_candidate->getBaseURL().c_str(),
-                    igd_candidate->getFriendlyName().c_str(),
-                    igd_candidate->getServiceType().c_str(),
-                    igd_candidate->getServiceId().c_str(),
-                    igd_candidate->getControlURL().c_str(),
-                    igd_candidate->getEventSubURL().c_str());
-
-        // Check if IGD is connected.
-        if (not actionIsIgdConnected(*igd_candidate)) {
-            JAMI_WARN("PUPnP: IGD candidate %s is not connected", igd_candidate->getUDN().c_str());
-            break;
-        }
-
-        // Validate external Ip.
-        igd_candidate->publicIp_ = actionGetExternalIP(*igd_candidate);
-        if (igd_candidate->publicIp_.toString().empty()) {
-            JAMI_WARN("PUPnP: IGD candidate %s has no valid external Ip", igd_candidate->getUDN().c_str());
-            break;
-        }
-
-        // Validate internal Ip.
-        igd_candidate->localIp_ = ip_utils::getLocalAddr(pj_AF_INET());
-        if (igd_candidate->localIp_.toString().empty()) {
-            JAMI_WARN("PUPnP: No valid internal Ip.");
-            break;
-        }
-
-        JAMI_DBG("PUPnP: Found device with external IP %s", igd_candidate->publicIp_.toString().c_str());
-
-        // Store public IP.
-        std::string publicIpStr(std::move(igd_candidate->publicIp_.toString()));
-
-        // Store info for subscription.
-        std::string eventSub = igd_candidate->getEventSubURL();
-        std::string udn = igd_candidate->getUDN();
-
-        // Remove any local mappings that may be left over from last time used.
-        removeAllLocalMappings(igd_candidate.get());
 
-        // Add the igd to the upnp context class list.
-        if (updateIgdListCb_(this, std::move(igd_candidate.get()), std::move(igd_candidate.get()->publicIp_), true)) {
-            JAMI_DBG("PUPnP: IGD with public IP %s was added to the list", publicIpStr.c_str());
-        } else {
-            JAMI_DBG("PUPnP: IGD with public IP %s is already in the list", publicIpStr.c_str());
-        }
+        // Check if we already downloaded the xml doc based on the igd location string.
+        std::string igdLocationUrl {UpnpDiscovery_get_Location_cstr(d_event)};
+        dwnldlXmlList_.emplace_back(dht::ThreadPool::io().get<IGDInfo>([this, location = std::move(igdLocationUrl)]{
+            IXML_Document* doc_container_ptr = nullptr;
+            XMLDocument doc_desc_ptr(nullptr, ixmlDocument_free);
+            int upnp_err = UpnpDownloadXmlDoc(location.c_str(), &doc_container_ptr);
+            if (doc_container_ptr)
+                doc_desc_ptr.reset(doc_container_ptr);
+            pupnpCv_.notify_all();
+            if (upnp_err != UPNP_E_SUCCESS or not doc_desc_ptr)
+                JAMI_WARN("PUPnP: Error downloading device XML document -> %s", UpnpGetErrorMessage(upnp_err));
+            else
+                return IGDInfo {std::move(location), std::move(doc_desc_ptr)};
+            return IGDInfo {std::move(location), XMLDocument(nullptr, ixmlDocument_free)};
+        }));
 
-        // Keep local IGD list internally.
-        if (cpDeviceList_.count(udn) > 0) {
-            cpDeviceList_[udn] = eventSub;
-        }
-        validIgdList_.emplace(std::move(igd_candidate->getUDN()), std::move(igd_candidate));
-
-        // Subscribe to IGD events.
-        upnp_err = UpnpSubscribeAsync(ctrlptHandle_, eventSub.c_str(), SUBSCRIBE_TIMEOUT, subEventCallback, this);
-        if (upnp_err != UPNP_E_SUCCESS) {
-            JAMI_WARN("PUPnP: Error when trying to request subscription for %s -> %s", udn.c_str(), UpnpGetErrorMessage(upnp_err));
-        }
         break;
     }
     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
     {
         const UpnpDiscovery *d_event = (const UpnpDiscovery *)event;
-
         std::lock_guard<std::mutex> lk(validIgdMutex_);
 
         // Remvoe device Id from list.
@@ -444,7 +467,6 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
 
         IGD* igd_to_remove = nullptr;
         for (auto it = validIgdList_.find(cpDeviceId); it != validIgdList_.end(); it++) {
-
             // Store igd to remove.
             igd_to_remove = it->second.get();
 
@@ -471,32 +493,17 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:   // This event will occur only if autorenewal is disabled.
     {
         const UpnpEventSubscribe *es_event = (const UpnpEventSubscribe *)event;
-
         std::string eventSubUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
 
-        std::pair<std::string, std::string> foundDevice = std::make_pair("", "");
-        bool foundEventSubUrl = false;
-        auto it = cpDeviceList_.begin();
-        while(it != cpDeviceList_.end()) {
-            if(it->second == eventSubUrl) {
-                foundEventSubUrl = true;
-                foundDevice = std::make_pair(it->first, it->second);
-                break;
-            }
-            it++;
-        }
-
-        if (not foundEventSubUrl) {
-            // If we don't find event subscription url then exit.
-            break;
+        auto it = validIgdList_.begin();
+        for (; it != validIgdList_.end(); it++) {
+            if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it->second))
+                if (igd->getEventSubURL() == eventSubUrl) {
+                    UpnpSubscribeAsync(ctrlptHandle_, eventSubUrl.c_str(), SUBSCRIBE_TIMEOUT, subEventCallback, this);
+                    break;
+                }
         }
 
-        std::string udn = foundDevice.first;
-        std::string eventSub = foundDevice.second;
-
-        // Renew subscriptons to IGD events.
-        UpnpSubscribeAsync(ctrlptHandle_, eventSub.c_str(), SUBSCRIBE_TIMEOUT, subEventCallback, this);
-
         break;
     }
     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
@@ -524,15 +531,14 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
 int
 PUPnP::subEventCallback(Upnp_EventType event_type, const void* event, void* user_data)
 {
-    if (auto pupnp = static_cast<PUPnP*>(user_data)) {
+    if (auto pupnp = static_cast<PUPnP*>(user_data))
         return pupnp->handleSubscriptionUPnPEvent(event_type, event);
-    }
     JAMI_WARN("PUPnP: Subscription callback without service Id string");
     return 0;
 }
 
 int
-PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event)
+PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType /*event_type */, const void* event)
 {
     std::lock_guard<std::mutex> lk(ctrlptMutex_);
 
@@ -550,9 +556,9 @@ PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event)
 }
 
 std::unique_ptr<UPnPIGD>
-PUPnP::parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event)
+PUPnP::parseIgd(IXML_Document* doc, std::string locationUrl)
 {
-    if (not doc or not d_event)
+    if (not (doc and locationUrl.c_str()))
         return nullptr;
 
     // Check the UDN to see if its already in our device list.
@@ -576,9 +582,8 @@ PUPnP::parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event)
 
     // Get base URL.
     std::string baseURL = getFirstDocItem(doc, "URLBase");
-    if (baseURL.empty()) {
-        baseURL = std::string(UpnpDiscovery_get_Location_cstr(d_event));
-    }
+    if (baseURL.empty())
+        baseURL = locationUrl;
 
     // Get list of services defined by serviceType.
     std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> serviceList(nullptr, ixmlNodeList_free);
@@ -627,11 +632,11 @@ PUPnP::parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event)
 
         char* absolute_control_url = nullptr;
         upnp_err = UpnpResolveURL2(baseURL.c_str(), controlURL.c_str(), &absolute_control_url);
-        if (upnp_err == UPNP_E_SUCCESS) {
+        if (upnp_err == UPNP_E_SUCCESS)
             controlURL = absolute_control_url;
-        } else {
+        else
             JAMI_WARN("PUPnP: Error resolving absolute controlURL -> %s", UpnpGetErrorMessage(upnp_err));
-        }
+
         std::free(absolute_control_url);
 
         // Get the relative eventSubURL and turn it into absolute address using the URLBase.
@@ -643,11 +648,11 @@ PUPnP::parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event)
 
         char* absolute_event_sub_url = nullptr;
         upnp_err = UpnpResolveURL2(baseURL.c_str(), eventSubURL.c_str(), &absolute_event_sub_url);
-        if (upnp_err == UPNP_E_SUCCESS) {
+        if (upnp_err == UPNP_E_SUCCESS)
             eventSubURL = absolute_event_sub_url;
-        } else {
+        else
             JAMI_WARN("PUPnP: Error resolving absolute eventSubURL -> %s", UpnpGetErrorMessage(upnp_err));
-        }
+
         std::free(absolute_event_sub_url);
 
         new_igd.reset(new UPnPIGD(std::move(UDN),
@@ -655,6 +660,7 @@ PUPnP::parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event)
                                   std::move(friendlyName),
                                   std::move(serviceType),
                                   std::move(serviceId),
+                                  std::move(locationUrl),
                                   std::move(controlURL),
                                   std::move(eventSubURL)));
 
@@ -667,9 +673,8 @@ PUPnP::parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event)
 bool
 PUPnP::actionIsIgdConnected(const UPnPIGD& igd)
 {
-    if (not clientRegistered_) {
+    if (not clientRegistered_)
         return false;
-    }
 
     // Action and response pointers.
     std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free);    // Action pointer.
@@ -701,9 +706,8 @@ PUPnP::actionIsIgdConnected(const UPnPIGD& igd)
 
     // Parse response.
     std::string status = getFirstDocItem(response.get(), "NewConnectionStatus");
-    if (status.compare("Connected") != 0) {
+    if (status.compare("Connected") != 0)
         return false;
-    }
 
     return true;
 }
@@ -711,9 +715,8 @@ PUPnP::actionIsIgdConnected(const UPnPIGD& igd)
 IpAddr
 PUPnP::actionGetExternalIP(const UPnPIGD& igd)
 {
-    if (not clientRegistered_) {
+    if (not clientRegistered_)
         return {};
-    }
 
     // Action and response pointers.
     std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free);    // Action pointer.
@@ -749,13 +752,8 @@ PUPnP::actionGetExternalIP(const UPnPIGD& igd)
 void
 PUPnP::actionDeletePortMappingsByDesc(const UPnPIGD& igd, const std::string& description)
 {
-    if (not clientRegistered_) {
-        return;
-    }
-
-    if (!igd.localIp_) {
+    if (not (clientRegistered_ and igd.localIp_))
         return;
-    }
 
     // Set action name.
     std::string action_name { "GetGenericPortMappingEntry" };
@@ -821,9 +819,8 @@ PUPnP::actionDeletePortMappingsByDesc(const UPnPIGD& igd, const std::string& des
 bool
 PUPnP::actionDeletePortMapping(const UPnPIGD& igd, const std::string& port_external, const std::string& protocol)
 {
-    if (not clientRegistered_) {
+    if (not clientRegistered_)
         return false;
-    }
 
     // Action and response pointers.
     std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free);    // Action pointer.
@@ -869,9 +866,8 @@ PUPnP::actionDeletePortMapping(const UPnPIGD& igd, const std::string& port_exter
 bool
 PUPnP::actionAddPortMapping(const UPnPIGD& igd, const Mapping& mapping, UPnPProtocol::UpnpError& error_code)
 {
-    if (not clientRegistered_) {
+    if (not clientRegistered_)
         return false;
-    }
 
     error_code = UPnPProtocol::UpnpError::ERROR_OK;
 
diff --git a/src/upnp/protocol/pupnp/pupnp.h b/src/upnp/protocol/pupnp/pupnp.h
index 52de8fa94690d39de1f2b3a3baf5894b6eb578f2..ca76786f22080b6fb0524bdec76cd8ac93db1830 100644
--- a/src/upnp/protocol/pupnp/pupnp.h
+++ b/src/upnp/protocol/pupnp/pupnp.h
@@ -2,7 +2,7 @@
  *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
  *
  *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
- *	Author: Eden Abitbol <eden.abitbol@savoirfairelinux.com>
+ *  Author: Eden Abitbol <eden.abitbol@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
@@ -50,6 +50,12 @@
 
 #include <atomic>
 #include <thread>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <memory>
+#include <future>
 
 namespace jami {
 class IpAddr;
@@ -68,6 +74,12 @@ constexpr static unsigned int SUBSCRIBE_TIMEOUT {300};
 class PUPnP : public UPnPProtocol
 {
 public:
+    using XMLDocument = std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>;
+    struct IGDInfo {
+        std::string location;
+        XMLDocument document;
+    };
+
     PUPnP();
     ~PUPnP();
 
@@ -75,10 +87,10 @@ public:
     Type getType() const override { return Type::PUPNP; }
 
     // Notifies a change in network.
-    void clearIGDs() override;
+    void clearIgds() override;
 
     // Sends out async search for IGD.
-    void searchForIGD() override;
+    void searchForIgd() override;
 
     // Tries to add mapping. Assumes mutex is already locked.
     Mapping addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, PortType type, UPnPProtocol::UpnpError& upnp_error) override;
@@ -90,8 +102,8 @@ public:
     void removeAllLocalMappings(IGD* igd) override;
 
 private:
-    // Register client in an async manner using a thread.
-    void registerClientAsync();
+    // Validate IGD from the xml document received from the router.
+    bool validateIgd(const IGDInfo&);
 
     // Control point callback.
     static int ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data);
@@ -116,7 +128,7 @@ private:
     int handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event);
 
     // Parses the IGD candidate.
-    std::unique_ptr<UPnPIGD> parseIGD(IXML_Document* doc, const UpnpDiscovery* d_event);
+    std::unique_ptr<UPnPIGD> parseIgd(IXML_Document* doc, std::string locationUrl);
 
     // These functions directly create UPnP actions and make synchronous UPnP control point calls. Assumes mutex is already locked.
     bool   actionIsIgdConnected(const UPnPIGD& igd);
@@ -133,11 +145,14 @@ private:
     std::thread pupnpThread_ {};                                // PUPnP thread for non-blocking client registration.
 
     std::map<std::string, std::shared_ptr<IGD>> validIgdList_;  // Map of valid IGDs with their UDN (universal Id).
-    std::map<std::string, std::string> cpDeviceList_;           // Control point device list containing the device ID and device subscription event url.
+    std::set<std::string> cpDeviceList_;                        // Control point device list containing the device ID and device subscription event url.
+    std::list<std::future<IGDInfo>> dwnldlXmlList_;      // List of shared_futures for blocking xml download function calls.
 
     std::mutex ctrlptMutex_;                                    // Mutex for client handle protection.
     UpnpClient_Handle ctrlptHandle_ {-1};                       // Control point handle.
+
     std::atomic_bool clientRegistered_ { false };               // Indicates of the client is registered.
+    std::atomic_bool searchForIgd_ { false };                   // Variable to signal thread for a search.
 };
 
 }} // namespace jami::upnp
\ No newline at end of file
diff --git a/src/upnp/protocol/pupnp/upnp_igd.cpp b/src/upnp/protocol/pupnp/upnp_igd.cpp
index a355ed1ce422a7969f2e5e54517e2657c114718b..3323799d40f450bac419faf24a15a097042764bd 100644
--- a/src/upnp/protocol/pupnp/upnp_igd.cpp
+++ b/src/upnp/protocol/pupnp/upnp_igd.cpp
@@ -23,15 +23,19 @@
 
 namespace jami { namespace upnp {
 
-UPnPIGD::UPnPIGD(std::string&& UDN, std::string&& baseURL, std::string&& friendlyName, std::string&& serviceType, std::string&& serviceId, std::string&& controlURL, std::string&& eventSubURL,
-    IpAddr&& localIp, IpAddr&& publicIp):
-    IGD(std::move(localIp), std::move(publicIp))
+UPnPIGD::UPnPIGD(std::string&& UDN, std::string&& baseURL, 
+                 std::string&& friendlyName, std::string&& serviceType, 
+                 std::string&& serviceId, std::string && locationURL,
+                 std::string&& controlURL, std::string&& eventSubURL,
+                 IpAddr&& localIp, IpAddr&& publicIp):
+                 IGD(std::move(localIp), std::move(publicIp))
 {
     UDN_ = std::move(UDN);
     baseURL_ = std::move(baseURL);
     friendlyName_ = std::move(friendlyName);
     serviceType_ = std::move(serviceType);
     serviceId_ = std::move(serviceId);
+    locationURL_ = std::move(locationURL);
     controlURL_ = std::move(controlURL);
     eventSubURL_ = std::move(eventSubURL);
 }
@@ -56,6 +60,7 @@ UPnPIGD::operator==(UPnPIGD& other) const
            friendlyName_ == other.friendlyName_ and
            serviceType_ == other.serviceType_ and
            serviceId_ == other.serviceId_ and
+           locationURL_ == other.locationURL_ and
            controlURL_ == other.controlURL_ and
            eventSubURL_ == other.eventSubURL_;
 }
diff --git a/src/upnp/protocol/pupnp/upnp_igd.h b/src/upnp/protocol/pupnp/upnp_igd.h
index 01638dcd1518166b5acb10926747b12986a1e056..22e51066296d3bf2520b3bdaece6e918e3905932 100644
--- a/src/upnp/protocol/pupnp/upnp_igd.h
+++ b/src/upnp/protocol/pupnp/upnp_igd.h
@@ -47,18 +47,20 @@ public:
             std::string&& friendlyName,
             std::string&& serviceType,
             std::string&& serviceId,
+            std::string&& locationURL,
             std::string&& controlURL,
             std::string&& eventSubURL,
             IpAddr&& localIp = {},
             IpAddr&& publicIp = {});
     ~UPnPIGD(){}
-    const std::string& getUDN() const          { return UDN_;          };
-    const std::string& getBaseURL() const      { return baseURL_;      };
-    const std::string& getFriendlyName() const { return friendlyName_; };
-    const std::string& getServiceType() const  { return serviceType_;  };
-    const std::string& getServiceId() const    { return serviceId_;    };
-    const std::string& getControlURL() const   { return controlURL_;   };
-    const std::string& getEventSubURL() const  { return eventSubURL_;  };
+    const std::string& getUDN() const          { return UDN_;          }
+    const std::string& getBaseURL() const      { return baseURL_;      }
+    const std::string& getFriendlyName() const { return friendlyName_; }
+    const std::string& getServiceType() const  { return serviceType_;  }
+    const std::string& getServiceId() const    { return serviceId_;    }
+    const std::string& getLocationURL() const  { return locationURL_;  }
+    const std::string& getControlURL() const   { return controlURL_;   }
+    const std::string& getEventSubURL() const  { return eventSubURL_;  }
 
     bool operator==(IGD& other) const;
     bool operator==(UPnPIGD& other) const;
@@ -69,6 +71,7 @@ private:
     std::string friendlyName_ {};
     std::string serviceType_ {};
     std::string serviceId_ {};
+    std::string locationURL_ {};
     std::string controlURL_ {};
     std::string eventSubURL_ {};
 };
diff --git a/src/upnp/protocol/upnp_protocol.h b/src/upnp/protocol/upnp_protocol.h
index b258163411e68e2d0fc0987e609a58661faf682e..e5ec27f7ccb17bdbf75771ae74d2f3b0e0684d16 100644
--- a/src/upnp/protocol/upnp_protocol.h
+++ b/src/upnp/protocol/upnp_protocol.h
@@ -73,10 +73,10 @@ public:
     virtual Type getType() const = 0;
 
     // Clear all known IGDs.
-    virtual void clearIGDs() = 0;
+    virtual void clearIgds() = 0;
 
     // Search for IGD.
-    virtual void searchForIGD() = 0;
+    virtual void searchForIgd() = 0;
 
     // Tries to add mapping. Assumes mutex is already locked.
     virtual Mapping addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, PortType type, UPnPProtocol::UpnpError& upnp_error) = 0;
diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp
index 0afafeefc0585bc16d4465b4871b5118a61dfab9..51f112e6333a9a30265fb42bb9c2a0d216bd7150 100644
--- a/src/upnp/upnp_context.cpp
+++ b/src/upnp/upnp_context.cpp
@@ -48,13 +48,13 @@ UPnPContext::UPnPContext()
 #if HAVE_LIBNATPMP
     auto natPmp = std::make_unique<NatPmp>();
     natPmp->setOnIgdChanged(std::bind(&UPnPContext::igdListChanged, this, _1, _2, _3, _4));
-    natPmp->searchForIGD();
+    natPmp->searchForIgd();
     protocolList_.push_back(std::move(natPmp));
 #endif
 #if HAVE_LIBUPNP
     auto pupnp = std::make_unique<PUPnP>();
     pupnp->setOnIgdChanged(std::bind(&UPnPContext::igdListChanged, this, _1, _2, _3, _4));
-    pupnp->searchForIGD();
+    pupnp->searchForIgd();
     protocolList_.push_back(std::move(pupnp));
 #endif
 }
@@ -70,7 +70,7 @@ UPnPContext::connectivityChanged()
     {
         std::lock_guard<std::mutex> lock(igdListMutex_);
         for (auto const& protocol : protocolList_)
-            protocol->clearIGDs();
+            protocol->clearIgds();
         if (not igdList_.empty()) {
             // Clear main IGD list.
             igdList_.clear();
@@ -81,7 +81,7 @@ UPnPContext::connectivityChanged()
     }
 
     for (auto const& protocol : protocolList_)
-        protocol->searchForIGD();
+        protocol->searchForIgd();
 }
 
 bool
@@ -306,6 +306,7 @@ UPnPContext::addIgdToList(UPnPProtocol* protocol, IGD* igd)
     }
 
     if (isIgdInList(igd->publicIp_)) {
+        JAMI_DBG("UPnPContext: IGD with public IP %s is already in the list", igd->publicIp_.toString().c_str());
         return false;
     }