diff --git a/src/AbstractDiskWriter.cc b/src/AbstractDiskWriter.cc
index 4d62bf97..ea4403fe 100644
--- a/src/AbstractDiskWriter.cc
+++ b/src/AbstractDiskWriter.cc
@@ -373,6 +373,14 @@ void AbstractDiskWriter::ensureMmapWrite(size_t len, int64_t offset)
         return;
       }
 
+      if (static_cast<uint64_t>(std::numeric_limits<size_t>::max()) <
+          static_cast<uint64_t>(filesize)) {
+        // filesize could overflow in 32bit OS with 64bit off_t type
+        // the filesize will be truncated if provided as a 32bit size_t
+        enableMmap_ = false;
+        return;
+      }
+
       int errNum = 0;
       if (static_cast<int64_t>(len + offset) <= filesize) {
 #ifdef __MINGW32__
@@ -391,11 +399,15 @@ void AbstractDiskWriter::ensureMmapWrite(size_t len, int64_t offset)
           errNum = GetLastError();
         }
 #else  // !__MINGW32__
-        mapaddr_ = reinterpret_cast<unsigned char*>(mmap(
-            nullptr, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0));
-        if (!mapaddr_) {
+        auto pa =
+            mmap(nullptr, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
+
+        if (pa == MAP_FAILED) {
           errNum = errno;
         }
+        else {
+          mapaddr_ = reinterpret_cast<unsigned char*>(pa);
+        }
 #endif // !__MINGW32__
         if (mapaddr_) {
           A2_LOG_DEBUG(fmt("Mapping file %s succeeded, length=%" PRId64 "",
diff --git a/src/BtInterestedMessage.cc b/src/BtInterestedMessage.cc
index c3c9fec2..bbf3a6ac 100644
--- a/src/BtInterestedMessage.cc
+++ b/src/BtInterestedMessage.cc
@@ -59,8 +59,12 @@ void BtInterestedMessage::doReceivedAction()
   if (isMetadataGetMode()) {
     return;
   }
-  getPeer()->peerInterested(true);
-  if (!getPeer()->amChoking()) {
+
+  auto& peer = getPeer();
+
+  peer->peerInterested(true);
+
+  if (peer->amChoking()) {
     peerStorage_->executeChoke();
   }
 }
diff --git a/src/BtLeecherStateChoke.cc b/src/BtLeecherStateChoke.cc
index 72d0be9f..3e57df53 100644
--- a/src/BtLeecherStateChoke.cc
+++ b/src/BtLeecherStateChoke.cc
@@ -152,10 +152,14 @@ void BtLeecherStateChoke::plannedOptimisticUnchoke(
                           PeerFilter(true, true));
   if (i != std::begin(peerEntries)) {
     std::shuffle(std::begin(peerEntries), i, *SimpleRandomizer::getInstance());
-    (*std::begin(peerEntries)).enableOptUnchoking();
+
+    auto& ent = *std::begin(peerEntries);
+    auto& peer = ent.getPeer();
+
+    ent.enableOptUnchoking();
+
     A2_LOG_INFO(
-        fmt("POU: %s",
-            (*std::begin(peerEntries)).getPeer()->getIPAddress().c_str()));
+        fmt("POU: %s:%u", peer->getIPAddress().c_str(), peer->getPort()));
   }
 }
 
@@ -165,35 +169,44 @@ void BtLeecherStateChoke::regularUnchoke(std::vector<PeerEntry>& peerEntries)
                              std::mem_fn(&PeerEntry::isRegularUnchoker));
 
   std::sort(std::begin(peerEntries), rest);
+  std::shuffle(rest, std::end(peerEntries), *SimpleRandomizer::getInstance());
 
   // the number of regular unchokers
   int count = 3;
 
   bool fastOptUnchoker = false;
   auto peerIter = std::begin(peerEntries);
-  for (; peerIter != rest && count; ++peerIter, --count) {
+  for (; peerIter != std::end(peerEntries) && count; ++peerIter, --count) {
+    auto& peer = peerIter->getPeer();
+
+    if (!peer->peerInterested()) {
+      continue;
+    }
+
     peerIter->disableChokingRequired();
-    A2_LOG_INFO(fmt("RU: %s, dlspd=%d",
-                    (*peerIter).getPeer()->getIPAddress().c_str(),
-                    (*peerIter).getDownloadSpeed()));
-    if (peerIter->getPeer()->optUnchoking()) {
+
+    A2_LOG_INFO(fmt("RU: %s:%u, dlspd=%d", peer->getIPAddress().c_str(),
+                    peer->getPort(), (*peerIter).getDownloadSpeed()));
+
+    if (peer->optUnchoking()) {
       fastOptUnchoker = true;
       peerIter->disableOptUnchoking();
     }
   }
   if (fastOptUnchoker) {
-    std::shuffle(peerIter, std::end(peerEntries),
-                 *SimpleRandomizer::getInstance());
     for (auto& p : peerEntries) {
-      if (p.getPeer()->peerInterested()) {
-        p.enableOptUnchoking();
-        A2_LOG_INFO(fmt("OU: %s", p.getPeer()->getIPAddress().c_str()));
-        break;
-      }
-      else {
-        p.disableChokingRequired();
-        A2_LOG_INFO(fmt("OU: %s", p.getPeer()->getIPAddress().c_str()));
+      if (!p.getPeer()->peerInterested()) {
+        continue;
       }
+
+      p.enableOptUnchoking();
+
+      auto& peer = p.getPeer();
+
+      A2_LOG_INFO(
+          fmt("OU: %s:%u", peer->getIPAddress().c_str(), peer->getPort()));
+
+      break;
     }
   }
 }
@@ -205,10 +218,18 @@ void BtLeecherStateChoke::executeChoke(const PeerSet& peerSet)
 
   std::vector<PeerEntry> peerEntries;
   for (const auto& p : peerSet) {
-    if (p->isActive() && !p->snubbing()) {
-      p->chokingRequired(true);
-      peerEntries.push_back(PeerEntry(p));
+    if (!p->isActive()) {
+      continue;
     }
+
+    p->chokingRequired(true);
+
+    if (p->snubbing()) {
+      p->optUnchoking(false);
+      continue;
+    }
+
+    peerEntries.push_back(PeerEntry(p));
   }
 
   // planned optimistic unchoke
diff --git a/src/BtSeederStateChoke.cc b/src/BtSeederStateChoke.cc
index 5662b6ac..4b44eac0 100644
--- a/src/BtSeederStateChoke.cc
+++ b/src/BtSeederStateChoke.cc
@@ -133,9 +133,12 @@ void BtSeederStateChoke::unchoke(
 
   auto r = std::begin(peers);
   for (; r != std::end(peers) && count; ++r, --count) {
-    (*r).getPeer()->chokingRequired(false);
-    A2_LOG_INFO(fmt("RU: %s, ulspd=%d", (*r).getPeer()->getIPAddress().c_str(),
-                    (*r).getUploadSpeed()));
+    auto& peer = (*r).getPeer();
+
+    peer->chokingRequired(false);
+
+    A2_LOG_INFO(fmt("RU: %s:%u, ulspd=%d", peer->getIPAddress().c_str(),
+                    peer->getPort(), (*r).getUploadSpeed()));
   }
 
   if (round_ < 2) {
@@ -143,8 +146,13 @@ void BtSeederStateChoke::unchoke(
                   std::mem_fn(&PeerEntry::disableOptUnchoking));
     if (r != std::end(peers)) {
       std::shuffle(r, std::end(peers), *SimpleRandomizer::getInstance());
-      (*r).getPeer()->optUnchoking(true);
-      A2_LOG_INFO(fmt("POU: %s", (*r).getPeer()->getIPAddress().c_str()));
+
+      auto& peer = (*r).getPeer();
+
+      peer->optUnchoking(true);
+
+      A2_LOG_INFO(
+          fmt("POU: %s:%u", peer->getIPAddress().c_str(), peer->getPort()));
     }
   }
 }
@@ -156,10 +164,17 @@ void BtSeederStateChoke::executeChoke(const PeerSet& peerSet)
 
   std::vector<PeerEntry> peerEntries;
   for (const auto& p : peerSet) {
-    if (p->isActive() && p->peerInterested()) {
-      p->chokingRequired(true);
-      peerEntries.push_back(PeerEntry(p));
+    if (!p->isActive()) {
+      continue;
     }
+
+    p->chokingRequired(true);
+    if (p->peerInterested()) {
+      peerEntries.push_back(PeerEntry(p));
+      continue;
+    }
+
+    p->optUnchoking(false);
   }
 
   unchoke(peerEntries);
diff --git a/src/DHTGetPeersCommand.cc b/src/DHTGetPeersCommand.cc
index 7bd6d477..fbb6e8ee 100644
--- a/src/DHTGetPeersCommand.cc
+++ b/src/DHTGetPeersCommand.cc
@@ -96,8 +96,7 @@ bool DHTGetPeersCommand::execute()
                     ((numRetry_ && elapsed >= GET_PEER_INTERVAL_RETRY) ||
                      elapsed >= GET_PEER_INTERVAL_LOW)) ||
                    (btRuntime_->getConnections() == 0 &&
-                    elapsed >= GET_PEER_INTERVAL_ZERO)) &&
-                  !requestGroup_->downloadFinished()))) {
+                    elapsed >= GET_PEER_INTERVAL_ZERO))))) {
     A2_LOG_DEBUG(fmt("Issuing PeerLookup for infoHash=%s",
                      bittorrent::getInfoHashString(
                          requestGroup_->getDownloadContext()).c_str()));
diff --git a/src/DHTGetPeersMessage.cc b/src/DHTGetPeersMessage.cc
index a2857e1d..ff5c8ba2 100644
--- a/src/DHTGetPeersMessage.cc
+++ b/src/DHTGetPeersMessage.cc
@@ -46,6 +46,10 @@
 #include "DHTTokenTracker.h"
 #include "DHTGetPeersReplyMessage.h"
 #include "util.h"
+#include "BtRegistry.h"
+#include "DownloadContext.h"
+#include "Option.h"
+#include "SocketCore.h"
 
 namespace aria2 {
 
@@ -59,18 +63,61 @@ DHTGetPeersMessage::DHTGetPeersMessage(
     const std::string& transactionID)
     : DHTQueryMessage{localNode, remoteNode, transactionID},
       peerAnnounceStorage_{nullptr},
-      tokenTracker_{nullptr}
+      tokenTracker_{nullptr},
+      btRegistry_{nullptr},
+      family_{AF_INET}
 {
   memcpy(infoHash_, infoHash, DHT_ID_LENGTH);
 }
 
+void DHTGetPeersMessage::addLocalPeer(std::vector<std::shared_ptr<Peer>>& peers)
+{
+  if (!btRegistry_) {
+    return;
+  }
+
+  auto& dctx = btRegistry_->getDownloadContext(
+      std::string(infoHash_, infoHash_ + DHT_ID_LENGTH));
+
+  if (!dctx) {
+    return;
+  }
+
+  auto group = dctx->getOwnerRequestGroup();
+  auto& option = group->getOption();
+  auto& externalIP = option->get(PREF_BT_EXTERNAL_IP);
+
+  if (externalIP.empty()) {
+    return;
+  }
+
+  std::array<uint8_t, sizeof(struct in6_addr)> dst;
+  if (inetPton(family_, externalIP.c_str(), dst.data()) == -1) {
+    return;
+  }
+
+  auto tcpPort = btRegistry_->getTcpPort();
+  if (std::find_if(std::begin(peers), std::end(peers),
+                   [&externalIP, tcpPort](const std::shared_ptr<Peer>& peer) {
+                     return peer->getIPAddress() == externalIP &&
+                            peer->getPort() == tcpPort;
+                   }) != std::end(peers)) {
+    return;
+  }
+
+  peers.push_back(std::make_shared<Peer>(externalIP, tcpPort));
+}
+
 void DHTGetPeersMessage::doReceivedAction()
 {
   std::string token = tokenTracker_->generateToken(
       infoHash_, getRemoteNode()->getIPAddress(), getRemoteNode()->getPort());
-  // Check to see localhost has the contents which has same infohash
   std::vector<std::shared_ptr<Peer>> peers;
   peerAnnounceStorage_->getPeers(peers, infoHash_);
+
+  // Check to see localhost has the contents which has same infohash
+  addLocalPeer(peers);
+
   std::vector<std::shared_ptr<DHTNode>> nodes;
   getRoutingTable()->getClosestKNodes(nodes, infoHash_);
   getMessageDispatcher()->addMessageToQueue(
@@ -102,6 +149,13 @@ void DHTGetPeersMessage::setTokenTracker(DHTTokenTracker* tokenTracker)
   tokenTracker_ = tokenTracker;
 }
 
+void DHTGetPeersMessage::setBtRegistry(BtRegistry* btRegistry)
+{
+  btRegistry_ = btRegistry;
+}
+
+void DHTGetPeersMessage::setFamily(int family) { family_ = family; }
+
 std::string DHTGetPeersMessage::toStringOptional() const
 {
   return "info_hash=" + util::toHex(infoHash_, INFO_HASH_LENGTH);
diff --git a/src/DHTGetPeersMessage.h b/src/DHTGetPeersMessage.h
index 529e8098..d4da04e8 100644
--- a/src/DHTGetPeersMessage.h
+++ b/src/DHTGetPeersMessage.h
@@ -36,6 +36,9 @@
 #define D_DHT_GET_PEERS_MESSAGE_H
 
 #include "DHTQueryMessage.h"
+
+#include <vector>
+
 #include "DHTConstants.h"
 #include "A2STR.h"
 
@@ -43,6 +46,8 @@ namespace aria2 {
 
 class DHTPeerAnnounceStorage;
 class DHTTokenTracker;
+class BtRegistry;
+class Peer;
 
 class DHTGetPeersMessage : public DHTQueryMessage {
 private:
@@ -52,6 +57,12 @@ private:
 
   DHTTokenTracker* tokenTracker_;
 
+  BtRegistry* btRegistry_;
+
+  int family_;
+
+  void addLocalPeer(std::vector<std::shared_ptr<Peer>>& peers);
+
 protected:
   virtual std::string toStringOptional() const CXX11_OVERRIDE;
 
@@ -73,6 +84,10 @@ public:
 
   void setTokenTracker(DHTTokenTracker* tokenTracker);
 
+  void setBtRegistry(BtRegistry* btRegistry);
+
+  void setFamily(int family);
+
   static const std::string GET_PEERS;
 
   static const std::string INFO_HASH;
diff --git a/src/DHTMessageFactoryImpl.cc b/src/DHTMessageFactoryImpl.cc
index 9e50fdd6..33994133 100644
--- a/src/DHTMessageFactoryImpl.cc
+++ b/src/DHTMessageFactoryImpl.cc
@@ -70,7 +70,8 @@ DHTMessageFactoryImpl::DHTMessageFactoryImpl(int family)
       dispatcher_{nullptr},
       routingTable_{nullptr},
       peerAnnounceStorage_{nullptr},
-      tokenTracker_{nullptr}
+      tokenTracker_{nullptr},
+      btRegistry_{nullptr}
 {
 }
 
@@ -409,6 +410,8 @@ DHTMessageFactoryImpl::createGetPeersMessage(
                                            transactionID);
   m->setPeerAnnounceStorage(peerAnnounceStorage_);
   m->setTokenTracker(tokenTracker_);
+  m->setBtRegistry(btRegistry_);
+  m->setFamily(family_);
   setCommonProperty(m.get());
   return m;
 }
@@ -529,4 +532,9 @@ void DHTMessageFactoryImpl::setLocalNode(
   localNode_ = localNode;
 }
 
+void DHTMessageFactoryImpl::setBtRegistry(BtRegistry* btRegistry)
+{
+  btRegistry_ = btRegistry;
+}
+
 } // namespace aria2
diff --git a/src/DHTMessageFactoryImpl.h b/src/DHTMessageFactoryImpl.h
index 0035d7cc..b6ac69f4 100644
--- a/src/DHTMessageFactoryImpl.h
+++ b/src/DHTMessageFactoryImpl.h
@@ -47,6 +47,7 @@ class DHTPeerAnnounceStorage;
 class DHTTokenTracker;
 class DHTMessage;
 class DHTAbstractMessage;
+class BtRegistry;
 
 class DHTMessageFactoryImpl : public DHTMessageFactory {
 private:
@@ -64,6 +65,8 @@ private:
 
   DHTTokenTracker* tokenTracker_;
 
+  BtRegistry* btRegistry_;
+
   // search node in routingTable. If it is not found, create new one.
   std::shared_ptr<DHTNode> getRemoteNode(const unsigned char* id,
                                          const std::string& ipaddr,
@@ -154,6 +157,8 @@ public:
   void setTokenTracker(DHTTokenTracker* tokenTracker);
 
   void setLocalNode(const std::shared_ptr<DHTNode>& localNode);
+
+  void setBtRegistry(BtRegistry* btRegistry);
 };
 
 } // namespace aria2
diff --git a/src/DHTSetup.cc b/src/DHTSetup.cc
index 45ec95a0..7ac7fc91 100644
--- a/src/DHTSetup.cc
+++ b/src/DHTSetup.cc
@@ -180,6 +180,7 @@ DHTSetup::setup(DownloadEngine* e, int family)
     factory->setPeerAnnounceStorage(peerAnnounceStorage.get());
     factory->setTokenTracker(tokenTracker.get());
     factory->setLocalNode(localNode);
+    factory->setBtRegistry(e->getBtRegistry().get());
 
     PrefPtr prefEntryPointHost = family == AF_INET ? PREF_DHT_ENTRY_POINT_HOST
                                                    : PREF_DHT_ENTRY_POINT_HOST6;
diff --git a/src/DHTTaskQueueImpl.cc b/src/DHTTaskQueueImpl.cc
index 0f05cfee..a5f96c29 100644
--- a/src/DHTTaskQueueImpl.cc
+++ b/src/DHTTaskQueueImpl.cc
@@ -40,7 +40,7 @@
 namespace aria2 {
 
 namespace {
-const size_t NUM_CONCURRENT_TASK = 5;
+const size_t NUM_CONCURRENT_TASK = 15;
 } // namespace
 
 DHTTaskQueueImpl::DHTTaskQueueImpl()
diff --git a/src/DefaultBtAnnounce.cc b/src/DefaultBtAnnounce.cc
index 7aaf223c..44e4d545 100644
--- a/src/DefaultBtAnnounce.cc
+++ b/src/DefaultBtAnnounce.cc
@@ -161,25 +161,24 @@ std::string DefaultBtAnnounce::getAnnounceUrl()
   const size_t keyLen = 8;
   std::string uri = announceList_.getAnnounce();
   uri += uriHasQuery(uri) ? "&" : "?";
-  uri +=
-      fmt("info_hash=%s&"
-          "peer_id=%s&"
-          "uploaded=%" PRId64 "&"
-          "downloaded=%" PRId64 "&"
-          "left=%" PRId64 "&"
-          "compact=1&"
-          "key=%s&"
-          "numwant=%d&"
-          "no_peer_id=1",
-          util::torrentPercentEncode(bittorrent::getInfoHash(downloadContext_),
-                                     INFO_HASH_LENGTH).c_str(),
-          util::torrentPercentEncode(bittorrent::getStaticPeerId(),
-                                     PEER_ID_LENGTH).c_str(),
-          stat.getSessionUploadLength(), stat.getSessionDownloadLength(), left,
-          util::torrentPercentEncode(bittorrent::getStaticPeerId() +
-                                         PEER_ID_LENGTH - keyLen,
-                                     keyLen).c_str(),
-          numWant);
+  uri += fmt("info_hash=%s&"
+             "peer_id=%s&"
+             "uploaded=%" PRId64 "&"
+             "downloaded=%" PRId64 "&"
+             "left=%" PRId64 "&"
+             "compact=1&"
+             "key=%s&"
+             "numwant=%d&"
+             "no_peer_id=1",
+             util::percentEncode(bittorrent::getInfoHash(downloadContext_),
+                                 INFO_HASH_LENGTH).c_str(),
+             util::percentEncode(bittorrent::getStaticPeerId(), PEER_ID_LENGTH)
+                 .c_str(),
+             stat.getSessionUploadLength(), stat.getSessionDownloadLength(),
+             left, util::percentEncode(bittorrent::getStaticPeerId() +
+                                           PEER_ID_LENGTH - keyLen,
+                                       keyLen).c_str(),
+             numWant);
   if (tcpPort_) {
     uri += fmt("&port=%u", tcpPort_);
   }
@@ -190,7 +189,7 @@ std::string DefaultBtAnnounce::getAnnounceUrl()
   }
   if (!trackerId_.empty()) {
     uri += "&trackerid=";
-    uri += util::torrentPercentEncode(trackerId_);
+    uri += util::percentEncode(trackerId_);
   }
   if (option_->getAsBool(PREF_BT_FORCE_ENCRYPTION) ||
       option_->getAsBool(PREF_BT_REQUIRE_CRYPTO)) {
diff --git a/src/DefaultPeerStorage.cc b/src/DefaultPeerStorage.cc
index 60e13a72..dc5353f6 100644
--- a/src/DefaultPeerStorage.cc
+++ b/src/DefaultPeerStorage.cc
@@ -284,14 +284,14 @@ void DefaultPeerStorage::returnPeer(const std::shared_ptr<Peer>& peer)
 bool DefaultPeerStorage::chokeRoundIntervalElapsed()
 {
   constexpr auto CHOKE_ROUND_INTERVAL = 10_s;
+
   if (pieceStorage_->downloadFinished()) {
     return seederStateChoke_->getLastRound().difference(global::wallclock()) >=
            CHOKE_ROUND_INTERVAL;
   }
-  else {
-    return leecherStateChoke_->getLastRound().difference(global::wallclock()) >=
-           CHOKE_ROUND_INTERVAL;
-  }
+
+  return leecherStateChoke_->getLastRound().difference(global::wallclock()) >=
+         CHOKE_ROUND_INTERVAL;
 }
 
 void DefaultPeerStorage::executeChoke()
diff --git a/src/PeerInteractionCommand.cc b/src/PeerInteractionCommand.cc
index 9d2898bc..b10fb3ce 100644
--- a/src/PeerInteractionCommand.cc
+++ b/src/PeerInteractionCommand.cc
@@ -340,43 +340,38 @@ bool PeerInteractionCommand::executeInternal()
       break;
     }
     case WIRED:
-      // See the comment for writable check below.
-      disableWriteCheckSocket();
-
       btInteractive_->doInteractionProcessing();
       if (btInteractive_->countReceivedMessageInIteration() > 0) {
         updateKeepAlive();
       }
-      if ((getPeer()->amInterested() && !getPeer()->peerChoking()) ||
-          btInteractive_->countOutstandingRequest() ||
-          (getPeer()->peerInterested() && !getPeer()->amChoking())) {
 
-        // Writable check to avoid slow seeding
-        if (btInteractive_->isSendingMessageInProgress()) {
-          setWriteCheckSocket(getSocket());
-        }
-
-        if (getDownloadEngine()
-                ->getRequestGroupMan()
-                ->doesOverallDownloadSpeedExceed() ||
-            requestGroup_->doesDownloadSpeedExceed()) {
-          disableReadCheckSocket();
-          setNoCheck(true);
-        }
-        else {
-          setReadCheckSocket(getSocket());
-        }
+      if (getDownloadEngine()
+              ->getRequestGroupMan()
+              ->doesOverallDownloadSpeedExceed() ||
+          requestGroup_->doesDownloadSpeedExceed()) {
+        disableReadCheckSocket();
+        setNoCheck(true);
       }
       else {
-        disableReadCheckSocket();
+        setReadCheckSocket(getSocket());
       }
+
       done = true;
       break;
     }
   }
-  if (btInteractive_->countPendingMessage() > 0) {
-    setNoCheck(true);
+  if ((btInteractive_->countPendingMessage() > 0 ||
+       btInteractive_->isSendingMessageInProgress()) &&
+      !getDownloadEngine()
+           ->getRequestGroupMan()
+           ->doesOverallUploadSpeedExceed() &&
+      !requestGroup_->doesUploadSpeedExceed()) {
+    setWriteCheckSocket(getSocket());
   }
+  else {
+    disableWriteCheckSocket();
+  }
+
   addCommandSelf();
   return false;
 }
diff --git a/src/usage_text.h b/src/usage_text.h
index e976eea1..65367346 100644
--- a/src/usage_text.h
+++ b/src/usage_text.h
@@ -538,9 +538,13 @@
 #define TEXT_EVENT_POLL                                                 \
   _(" --event-poll=POLL            Specify the method for polling events.")
 #define TEXT_BT_EXTERNAL_IP                                             \
-  _(" --bt-external-ip=IPADDRESS   Specify the external IP address to report to a\n" \
-    "                              BitTorrent tracker. Although this function is\n" \
-    "                              named 'external', it can accept any kind of IP\n" \
+  _(" --bt-external-ip=IPADDRESS   Specify the external IP address to use in\n" \
+    "                              BitTorrent download and DHT. It may be sent to\n" \
+    "                              BitTorrent tracker. For DHT, this option should\n" \
+    "                              be set to report that local node is downloading\n" \
+    "                              a particular torrent. This is critical to use\n" \
+    "                              DHT in a private network. Although this function\n" \
+    "                              is named 'external', it can accept any kind of IP\n" \
     "                              addresses.")
 #define TEXT_HTTP_AUTH_CHALLENGE                                        \
   _(" --http-auth-challenge[=true|false] Send HTTP authorization header only when it\n" \
diff --git a/test/BtInterestedMessageTest.cc b/test/BtInterestedMessageTest.cc
index 79e73216..732aa1a1 100644
--- a/test/BtInterestedMessageTest.cc
+++ b/test/BtInterestedMessageTest.cc
@@ -83,7 +83,7 @@ void BtInterestedMessageTest::testDoReceivedAction()
   CPPUNIT_ASSERT(!peer->peerInterested());
   msg.doReceivedAction();
   CPPUNIT_ASSERT(peer->peerInterested());
-  CPPUNIT_ASSERT_EQUAL(0, peerStorage->getNumChokeExecuted());
+  CPPUNIT_ASSERT_EQUAL(1, peerStorage->getNumChokeExecuted());
 
   peer->amChoking(false);
   msg.doReceivedAction();
diff --git a/test/DHTGetPeersMessageTest.cc b/test/DHTGetPeersMessageTest.cc
index 07aecc08..cac07ca4 100644
--- a/test/DHTGetPeersMessageTest.cc
+++ b/test/DHTGetPeersMessageTest.cc
@@ -12,6 +12,12 @@
 #include "DHTPeerAnnounceStorage.h"
 #include "DHTRoutingTable.h"
 #include "bencode2.h"
+#include "GroupId.h"
+#include "DownloadContext.h"
+#include "Option.h"
+#include "RequestGroup.h"
+#include "BtRegistry.h"
+#include "TorrentAttribute.h"
 
 namespace aria2 {
 
@@ -102,11 +108,32 @@ void DHTGetPeersMessageTest::testDoReceivedAction()
   factory.setLocalNode(localNode_);
   DHTRoutingTable routingTable(localNode_);
 
+  auto torrentAttrs = std::make_shared<TorrentAttribute>();
+  torrentAttrs->infoHash = std::string(infoHash, infoHash + DHT_ID_LENGTH);
+
+  auto dctx = std::make_shared<DownloadContext>();
+  dctx->setAttribute(CTX_ATTR_BT, torrentAttrs);
+
+  auto option = std::make_shared<Option>();
+  option->put(PREF_BT_EXTERNAL_IP, "192.168.0.1");
+
+  auto gid = GroupId::create();
+  RequestGroup group(gid, option);
+  dctx->setOwnerRequestGroup(&group);
+
+  BtRegistry btReg;
+  btReg.put(
+      gid->getNumericId(),
+      make_unique<BtObject>(dctx, nullptr, nullptr, nullptr, nullptr, nullptr));
+  btReg.setTcpPort(6890);
+
   DHTGetPeersMessage msg(localNode_, remoteNode_, infoHash, transactionID);
   msg.setRoutingTable(&routingTable);
   msg.setTokenTracker(&tokenTracker);
   msg.setMessageDispatcher(&dispatcher);
   msg.setMessageFactory(&factory);
+  msg.setBtRegistry(&btReg);
+  msg.setFamily(AF_INET);
   {
     // localhost has peer contact information for that infohash.
     DHTPeerAnnounceStorage peerAnnounceStorage;
@@ -129,7 +156,7 @@ void DHTGetPeersMessageTest::testDoReceivedAction()
                                                     remoteNode_->getPort()),
                          m->getToken());
     CPPUNIT_ASSERT_EQUAL((size_t)0, m->getClosestKNodes().size());
-    CPPUNIT_ASSERT_EQUAL((size_t)2, m->getValues().size());
+    CPPUNIT_ASSERT_EQUAL((size_t)3, m->getValues().size());
     {
       auto peer = m->getValues()[0];
       CPPUNIT_ASSERT_EQUAL(std::string("192.168.0.100"), peer->getIPAddress());
@@ -140,7 +167,13 @@ void DHTGetPeersMessageTest::testDoReceivedAction()
       CPPUNIT_ASSERT_EQUAL(std::string("192.168.0.101"), peer->getIPAddress());
       CPPUNIT_ASSERT_EQUAL((uint16_t)6889, peer->getPort());
     }
+    {
+      auto peer = m->getValues()[2];
+      CPPUNIT_ASSERT_EQUAL(std::string("192.168.0.1"), peer->getIPAddress());
+      CPPUNIT_ASSERT_EQUAL((uint16_t)6890, peer->getPort());
+    }
   }
+  msg.setBtRegistry(nullptr);
   dispatcher.messageQueue_.clear();
   {
     // localhost doesn't have peer contact information for that infohash.
diff --git a/test/DefaultBtAnnounceTest.cc b/test/DefaultBtAnnounceTest.cc
index b5e1d4ee..1fafaf47 100644
--- a/test/DefaultBtAnnounceTest.cc
+++ b/test/DefaultBtAnnounceTest.cc
@@ -158,7 +158,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -169,7 +169,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&supportcrypto=1"),
       btAnnounce.getAnnounceUrl());
@@ -179,7 +179,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://backup/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -192,7 +192,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=completed&"
                   "supportcrypto=1"),
@@ -203,7 +203,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://backup/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=completed&"
                   "supportcrypto=1"),
@@ -216,7 +216,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=0&no_peer_id=1&port=6989&event=stopped&"
                   "supportcrypto=1"),
@@ -227,7 +227,7 @@ void DefaultBtAnnounceTest::testNoMoreAnnounce()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://backup/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=0&no_peer_id=1&port=6989&event=stopped&"
                   "supportcrypto=1"),
@@ -255,7 +255,7 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -281,7 +281,7 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&supportcrypto=1"),
       btAnnounce.getAnnounceUrl());
@@ -296,7 +296,7 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=completed&"
                   "supportcrypto=1"),
@@ -312,7 +312,7 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=0&no_peer_id=1&port=6989&event=stopped&"
                   "supportcrypto=1"),
@@ -339,7 +339,7 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl_withQuery()
       std::string(
           "http://localhost/announce?k=v&"
           "info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%01%23Eg&"
-          "peer_id=%2Daria2%2Dultrafastdltl&"
+          "peer_id=-aria2-ultrafastdltl&"
           "uploaded=1572864&downloaded=1310720&left=1572864&compact=1&"
           "key=fastdltl&numwant=50&no_peer_id=1&port=6989&event=started&"
           "supportcrypto=1"),
@@ -364,7 +364,7 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl_externalIP()
       std::string(
           "http://localhost/announce?"
           "info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%01%23Eg&"
-          "peer_id=%2Daria2%2Dultrafastdltl&"
+          "peer_id=-aria2-ultrafastdltl&"
           "uploaded=1572864&downloaded=1310720&left=1572864&compact=1&"
           "key=fastdltl&numwant=50&no_peer_id=1&port=6989&event=started&"
           "supportcrypto=1&ip=192.168.1.1"),
@@ -395,7 +395,7 @@ void DefaultBtAnnounceTest::testIsAllAnnounceFailed()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -406,7 +406,7 @@ void DefaultBtAnnounceTest::testIsAllAnnounceFailed()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://backup/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -442,7 +442,7 @@ void DefaultBtAnnounceTest::testURLOrderInStoppedEvent()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost1/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -455,7 +455,7 @@ void DefaultBtAnnounceTest::testURLOrderInStoppedEvent()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost1/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=0&no_peer_id=1&port=6989&event=stopped&"
                   "supportcrypto=1"),
@@ -466,7 +466,7 @@ void DefaultBtAnnounceTest::testURLOrderInStoppedEvent()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost2/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=0&no_peer_id=1&port=6989&event=stopped&"
                   "supportcrypto=1"),
@@ -494,7 +494,7 @@ void DefaultBtAnnounceTest::testURLOrderInCompletedEvent()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost1/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=started&"
                   "supportcrypto=1"),
@@ -507,7 +507,7 @@ void DefaultBtAnnounceTest::testURLOrderInCompletedEvent()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost1/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=completed&"
                   "supportcrypto=1"),
@@ -518,7 +518,7 @@ void DefaultBtAnnounceTest::testURLOrderInCompletedEvent()
   CPPUNIT_ASSERT_EQUAL(
       std::string("http://localhost2/"
                   "announce?info_hash=%01%23Eg%89%AB%CD%EF%01%23Eg%89%AB%CD%EF%"
-                  "01%23Eg&peer_id=%2Daria2%2Dultrafastdltl&uploaded=1572864&"
+                  "01%23Eg&peer_id=-aria2-ultrafastdltl&uploaded=1572864&"
                   "downloaded=1310720&left=1572864&compact=1&key=fastdltl&"
                   "numwant=50&no_peer_id=1&port=6989&event=completed&"
                   "supportcrypto=1"),