diff --git a/include/opendht/dht.h b/include/opendht/dht.h
index 36d595f1db3134510e5b93b8bc689377fa3252fe..2194d8e1f47600e16d0f19cae74ce0a7f77c3330 100644
--- a/include/opendht/dht.h
+++ b/include/opendht/dht.h
@@ -41,8 +41,11 @@ THE SOFTWARE.
 
 namespace dht {
 
+using Address = std::pair<sockaddr_storage, socklen_t>;
+
 std::string print_addr(const sockaddr* sa, socklen_t slen);
 std::string print_addr(const sockaddr_storage& ss, socklen_t sslen);
+std::string printAddr(const Address& addr);
 
 struct NodeExport {
     InfoHash id;
@@ -332,6 +335,8 @@ public:
     /* This must be provided by the user. */
     static bool isBlacklisted(const sockaddr*, socklen_t) { return false; }
 
+    std::vector<Address> getPublicAddress();
+
 protected:
     LogMethod DHT_DEBUG = NOLOG;
     LogMethod DHT_WARN = NOLOG;
@@ -734,6 +739,7 @@ private:
     sockaddr_storage blacklist[BLACKLISTED_MAX] {};
     unsigned next_blacklisted = 0;
 
+    // timing
     time_point now;
     time_point mybucket_grow_time {time_point::min()}, mybucket6_grow_time {time_point::min()};
     time_point expire_stuff_time {time_point::min()};
@@ -742,6 +748,9 @@ private:
     time_point rotate_secrets_time {time_point::min()};
     std::queue<time_point> rate_limit_time {};
 
+    using ReportedAddr = std::pair<unsigned, Address>;
+    std::vector<ReportedAddr> reported_addr;
+
     // Networking & packet handling
     int send(const char* buf, size_t len, int flags, const sockaddr*, socklen_t);
     int sendPing(const sockaddr*, socklen_t, TransId tid);
@@ -784,13 +793,16 @@ private:
                   uint8_t *nodes_return, unsigned *nodes_len,
                   uint8_t *nodes6_return, unsigned *nodes6_len,
                   std::vector<std::shared_ptr<Value>>& values_return,
-                             want_t* want_return, uint16_t& error_code, bool& ring);
+                  want_t* want_return, uint16_t& error_code, bool& ring,
+                  sockaddr* addr_return, socklen_t& addr_length_return);
 
     void rotateSecrets();
 
     Blob makeToken(const sockaddr *sa, bool old) const;
     bool tokenMatch(const Blob& token, const sockaddr *sa) const;
 
+    void reportedAddr(const sockaddr *sa, socklen_t sa_len);
+
     // Storage
     Storage* findStorage(const InfoHash& id);
     const Storage* findStorage(const InfoHash& id) const {
@@ -828,7 +840,7 @@ private:
     void dumpBucket(const Bucket& b, std::ostream& out) const;
 
     // Nodes
-    std::shared_ptr<Node> newNode(const InfoHash& id, const sockaddr*, socklen_t, int confirm);
+    std::shared_ptr<Node> newNode(const InfoHash& id, const sockaddr*, socklen_t, int confirm, const sockaddr* addr=nullptr, socklen_t addr_length=0);
     std::shared_ptr<Node> findNode(const InfoHash& id, sa_family_t af);
     const std::shared_ptr<Node> findNode(const InfoHash& id, sa_family_t af) const;
     bool trySearchInsert(const std::shared_ptr<Node>& node);
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index 036aea9eff39bdfb153e71d7c3a83879b38b7661..eab88bbf8a8968847de3935f91d32e387cc83da7 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -278,6 +278,18 @@ public:
         std::lock_guard<std::mutex> lck(dht_mtx);
         return dht_->getSearchesLog(af);
     }
+    std::vector<Address> getPublicAddress()
+    {
+        std::lock_guard<std::mutex> lck(dht_mtx);
+        return dht_->getPublicAddress();
+    }
+    std::vector<std::string> getPublicAddressStr()
+    {
+        auto addrs = getPublicAddress();
+        std::vector<std::string> ret(addrs.size());
+        std::transform(addrs.begin(), addrs.end(), ret.begin(), dht::printAddr);
+        return ret;
+    }
 
     // securedht methods
 
diff --git a/src/dht.cpp b/src/dht.cpp
index a9452c18465b19bd6c902ab7a2c32919907bd1bd..84a6a89e58b5fc0acf719f38c1066b2b1779a27c 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -109,9 +109,11 @@ dht::print_addr(const sockaddr* sa, socklen_t slen)
     std::stringstream out;
     if (!getnameinfo(sa, slen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
         if (sa->sa_family == AF_INET6)
-            out << "[" << hbuf << "]:" << sbuf;
+            out << "[" << hbuf << "]";
         else
-            out << hbuf << ":" << sbuf;
+            out << hbuf;
+        if (strcmp(sbuf, "0"))
+            out << ":" << sbuf;
     } else
         out << "[invalid address]";
     return out.str();
@@ -123,6 +125,11 @@ dht::print_addr(const sockaddr_storage& ss, socklen_t sslen)
     return print_addr((const sockaddr*)&ss, sslen);
 }
 
+std::string
+dht::printAddr(const Address& addr) {
+    return print_addr((const sockaddr*)&addr.first, addr.second);
+}
+
 template <class DT>
 static double
 print_dt(DT d) {
@@ -496,6 +503,19 @@ Dht::isNodeBlacklisted(const sockaddr *sa, socklen_t salen) const
     return false;
 }
 
+std::vector<Address>
+Dht::getPublicAddress()
+{
+    std::sort(reported_addr.begin(), reported_addr.end(), [](const ReportedAddr& a, const ReportedAddr& b) {
+        return a.first < b.first;
+    });
+    std::vector<Address> ret;
+    ret.reserve(reported_addr.size());
+    for (const auto& addr : reported_addr)
+        ret.emplace_back(addr.second);
+    return ret;
+}
+
 /* Split a bucket into two equal parts. */
 bool
 Dht::RoutingTable::split(const RoutingTable::iterator& b)
@@ -540,10 +560,23 @@ Dht::trySearchInsert(const std::shared_ptr<Node>& node)
     return inserted;
 }
 
+void 
+Dht::reportedAddr(const sockaddr *sa, socklen_t sa_len)
+{
+    auto it = std::find_if(reported_addr.begin(), reported_addr.end(), [=](const ReportedAddr& addr){
+        return (addr.second.second == sa_len) && std::equal((uint8_t*)&addr.second.first, (uint8_t*)&addr.second.first + addr.second.second, (uint8_t*)sa);
+    });
+    if (it == reported_addr.end()) {
+        if (reported_addr.size() < 32)
+            reported_addr.emplace_back(1, std::make_pair(*((sockaddr_storage*)sa), sa_len));
+    } else
+        it->first++;
+}
+
 /* We just learnt about a node, not necessarily a new one.  Confirm is 1 if
    the node sent a message, 2 if it sent us a reply. */
 std::shared_ptr<Node>
-Dht::newNode(const InfoHash& id, const sockaddr *sa, socklen_t salen, int confirm)
+Dht::newNode(const InfoHash& id, const sockaddr *sa, socklen_t salen, int confirm, const sockaddr* addr, socklen_t addr_length)
 {
     if (id == myid || isMartian(sa, salen) || isNodeBlacklisted(sa, salen))
         return nullptr;
@@ -555,8 +588,11 @@ Dht::newNode(const InfoHash& id, const sockaddr *sa, socklen_t salen, int confir
 
     bool mybucket = list.contains(b, myid);
 
-    if (confirm == 2)
+    if (confirm == 2) {
         b->time = now;
+        if (addr and addr_length)
+            reportedAddr(addr, addr_length);
+    }
 
     for (auto& n : b->nodes) {
         if (n->id != id) continue;
@@ -1717,6 +1753,7 @@ Dht::connectivityChanged()
     confirm_nodes_time = now;
     mybucket_grow_time = now;
     mybucket6_grow_time = now;
+    reported_addr.clear();
     for (auto& s : searches)
         for (auto& sn : s.nodes)
             sn.listenStatus = {};
@@ -2154,6 +2191,8 @@ Dht::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, soc
     in_port_t port;
     Value::Id value_id;
     uint16_t error_code;
+    sockaddr_storage addr;
+    socklen_t addr_length = sizeof(sockaddr_storage);
 
     std::vector<std::shared_ptr<Value>> values;
 
@@ -2176,7 +2215,7 @@ Dht::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, soc
         message = parseMessage(buf, buflen, tid, id, info_hash, target,
                                 port, token, value_id,
                                 nodes, &nodes_len, nodes6, &nodes6_len,
-                               values, &want, error_code, ring);
+                               values, &want, error_code, ring, (sockaddr*)&addr, addr_length);
         if (message != MessageType::Error && id == zeroes)
             throw DhtException("no or invalid InfoHash");
     } catch (const std::exception& e) {
@@ -2241,7 +2280,7 @@ Dht::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, soc
         }
         if (tid.matches(TransPrefix::PING)) {
             DHT_DEBUG("Pong!");
-            newNode(id, from, fromlen, 2);
+            newNode(id, from, fromlen, 2, (sockaddr*)&addr, addr_length);
         } else if (tid.matches(TransPrefix::FIND_NODE) or tid.matches(TransPrefix::GET_VALUES)) {
             bool gp = false;
             Search *sr = nullptr;
@@ -2259,7 +2298,7 @@ Dht::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, soc
                 DHT_WARN("Unknown search with tid %u !", ttid);
                 n = newNode(id, from, fromlen, 1);
             } else {
-                n = newNode(id, from, fromlen, 2);
+                n = newNode(id, from, fromlen, 2, (sockaddr*)&addr, addr_length);
                 for (unsigned i = 0; i < nodes_len / 26; i++) {
                     uint8_t *ni = nodes + i * 26;
                     const InfoHash& ni_id = *reinterpret_cast<InfoHash*>(ni);
@@ -2336,7 +2375,7 @@ Dht::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, soc
                 DHT_DEBUG("Unknown search or announce!");
                 newNode(id, from, fromlen, 1);
             } else {
-                auto n = newNode(id, from, fromlen, 2);
+                auto n = newNode(id, from, fromlen, 2, (sockaddr*)&addr, addr_length);
                 for (auto& sn : sr->nodes)
                     if (sn.node == n) {
                         auto it = sn.acked.emplace(value_id, SearchNode::RequestStatus{});
@@ -2364,7 +2403,7 @@ Dht::processMessage(const uint8_t *buf, size_t buflen, const sockaddr *from, soc
                 DHT_DEBUG("Unknown search or announce!");
                 newNode(id, from, fromlen, 1);
             } else {
-                auto n = newNode(id, from, fromlen, 2);
+                auto n = newNode(id, from, fromlen, 2, (sockaddr*)&addr, addr_length);
                 for (auto& sn : sr->nodes)
                     if (sn.node == n) {
                         sn.listenStatus.reply_time = now;
@@ -2698,13 +2737,25 @@ Dht::sendPing(const sockaddr *sa, socklen_t salen, TransId tid)
     return send(buf, i, 0, sa, salen);
 }
 
+void
+insertAddr(char* buf, size_t buflen, size_t& p, const sockaddr *sa, socklen_t)
+{
+    size_t addr_len = (sa->sa_family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr);
+    void* addr_ptr = (sa->sa_family == AF_INET) ? (void*)&((sockaddr_in*)sa)->sin_addr
+                                                : (void*)&((sockaddr_in6*)sa)->sin6_addr;
+    int rc = snprintf(buf + p, buflen - p, "2:sa%lu:", addr_len);
+    INC(p, rc, buflen);
+    COPY(buf, p, addr_ptr, addr_len, buflen);
+}
+
 int
 Dht::sendPong(const sockaddr *sa, socklen_t salen, TransId tid)
 {
     char buf[512];
-    int i = 0, rc;
-    rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
+    size_t i = 0;
+    auto rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
     COPY(buf, i, myid.data(), myid.size(), 512);
+    insertAddr(buf, 512, i, sa, salen);
     rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid.length); INC(i, rc, 512);
     COPY(buf, i, tid.data(), tid.length, 512);
     ADD_V(buf, i, 512);
@@ -2745,10 +2796,11 @@ Dht::sendNodesValues(const sockaddr *sa, socklen_t salen, TransId tid,
 {
     constexpr const size_t BUF_SZ = 2048 * 64;
     char buf[BUF_SZ];
-    int i = 0, rc;
+    size_t i = 0;
 
-    rc = snprintf(buf + i, BUF_SZ - i, "d1:rd2:id20:"); INC(i, rc, BUF_SZ);
+    auto rc = snprintf(buf + i, BUF_SZ - i, "d1:rd2:id20:"); INC(i, rc, BUF_SZ);
     COPY(buf, i, myid.data(), myid.size(), BUF_SZ);
+    insertAddr(buf, BUF_SZ, i, sa, salen);
     if (nodes_len > 0) {
         rc = snprintf(buf + i, BUF_SZ - i, "5:nodes%u:", nodes_len);
         INC(i, rc, BUF_SZ);
@@ -2951,10 +3003,12 @@ Dht::sendListenConfirmation(const sockaddr* sa, socklen_t salen, TransId tid)
 {
     static constexpr const size_t BUF_SZ = 512;
     char buf[BUF_SZ];
-    int i = 0, rc;
+    size_t i = 0;
 
-    rc = snprintf(buf + i, BUF_SZ - i, "d1:rd2:id20:"); INC(i, rc, BUF_SZ);
+    auto rc = snprintf(buf + i, BUF_SZ - i, "d1:rd2:id20:"); INC(i, rc, BUF_SZ);
     COPY(buf, i, myid.data(), myid.size(), BUF_SZ);
+    insertAddr(buf, BUF_SZ, i, sa, salen);
+
     rc = snprintf(buf + i, BUF_SZ - i, "e1:t%u:", tid.length); INC(i, rc, BUF_SZ);
     COPY(buf, i, tid.data(), tid.length, BUF_SZ);
     ADD_V(buf, i, BUF_SZ);
@@ -2994,10 +3048,12 @@ int
 Dht::sendValueAnnounced(const sockaddr *sa, socklen_t salen, TransId tid, Value::Id vid)
 {
     char buf[512];
-    int i = 0, rc;
+    size_t i = 0;
 
-    rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
+    auto rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
     COPY(buf, i, myid.data(), myid.size(), 512);
+    insertAddr(buf, 512, i, sa, salen);
+
     rc = snprintf(buf + i, 512 - i, "3:vid%lu:", sizeof(Value::Id)); INC(i, rc, 512);
     COPY(buf, i, &vid, sizeof(Value::Id), 512);
     rc = snprintf(buf + i, 512 - i, "e1:t%u:", tid.length); INC(i, rc, 512);
@@ -3044,7 +3100,8 @@ Dht::parseMessage(const uint8_t *buf, size_t buflen,
               uint8_t *nodes_return, unsigned *nodes_len,
               uint8_t *nodes6_return, unsigned *nodes6_len,
               std::vector<std::shared_ptr<Value>>& values_return,
-                  want_t* want_return, uint16_t& error_code, bool& ring)
+              want_t* want_return, uint16_t& error_code, bool& ring,
+              sockaddr* addr_return, socklen_t& addr_length_return)
 {
     const uint8_t *p;
 
@@ -3073,6 +3130,34 @@ Dht::parseMessage(const uint8_t *buf, size_t buflen,
         id_return = {};
     }
 
+    if (addr_return and addr_length_return) {
+        p = (uint8_t*)dht_memmem(buf, buflen, "2:sa", 4);
+        if (p) {
+            char *q;
+            size_t l = strtoul((char*)p + 4, &q, 10);
+            if (q && *q == ':' && (l == sizeof(in_addr) or l == sizeof(in6_addr))) {
+                CHECK(q + 1, l);
+                if (l == sizeof(in_addr)) {
+                    auto addr = (sockaddr_in*)addr_return;
+                    std::fill_n((uint8_t*)addr, sizeof(sockaddr_in), 0);
+                    addr->sin_family = AF_INET;
+                    addr->sin_port = 0;
+                    memcpy(&addr->sin_addr, q+1, l);
+                    addr_length_return = sizeof(sockaddr_in);
+                } else if (l == sizeof(in6_addr)) {
+                    auto addr = (sockaddr_in6*)addr_return;
+                    std::fill_n((uint8_t*)addr, sizeof(sockaddr_in6), 0);
+                    addr_return->sa_family = AF_INET6;
+                    addr->sin6_port = 0;
+                    memcpy(&addr->sin6_addr, q+1, l);
+                    addr_length_return = sizeof(sockaddr_in6);
+                }
+            } else
+                addr_length_return = 0;
+        } else
+            addr_length_return = 0;
+    }
+
     p = (uint8_t*)dht_memmem(buf, buflen, "9:info_hash20:", 14);
     if (p) {
         CHECK(p + 14, HASH_LEN);
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index fd38edc7ed876e0eb8fd087b8f187a696abdaa73..346b816ae4938aaba046d6aeb74eb05d386ebfa4 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -142,6 +142,12 @@ main(int argc, char **argv)
             std::cout << "Searches:" << std::endl;
             std::cout << dht.getSearchesLog() << std::endl;
             continue;
+        } else if (op == "la")  {
+            std::cout << "Reported public addresses:" << std::endl;
+            auto addrs = dht.getPublicAddressStr();
+            for (const auto& addr : addrs)
+                std::cout << addr << std::endl;
+            continue;
         } else if (op == "log") {
             do_log = !do_log;
             if (do_log)