Forráskód Böngészése

Support UDP tracker

It shares UDP listening port with IPv4 DHT. At the moment, in order to
enable UDP tracker support, enable IPv4 DHT.
Tatsuhiro Tsujikawa 12 éve
szülő
commit
d68741697a

+ 10 - 0
src/BtAnnounce.h

@@ -40,9 +40,12 @@
 #include <string>
 
 #include "a2time.h"
+#include "SharedHandle.h"
 
 namespace aria2 {
 
+class UDPTrackerRequest;
+
 class BtAnnounce {
 public:
   virtual ~BtAnnounce() {}
@@ -65,6 +68,10 @@ public:
    */
   virtual std::string getAnnounceUrl() = 0;
 
+  virtual SharedHandle<UDPTrackerRequest>
+  createUDPTrackerRequest(const std::string& remoteAddr, uint16_t remotePort,
+                          uint16_t localPort) = 0;
+
   /**
    * Tells that the announce process has just started.
    */
@@ -96,6 +103,9 @@ public:
   virtual void processAnnounceResponse(const unsigned char* trackerResponse,
                                        size_t trackerResponseLength) = 0;
 
+  virtual void processUDPTrackerResponse
+  (const SharedHandle<UDPTrackerRequest>& req) = 0;
+
   /**
    * Returns true if no more announce is needed.
    */

+ 9 - 1
src/BtRegistry.cc

@@ -42,12 +42,14 @@
 #include "BtProgressInfoFile.h"
 #include "bittorrent_helper.h"
 #include "LpdMessageReceiver.h"
+#include "UDPTrackerClient.h"
 #include "NullHandle.h"
 
 namespace aria2 {
 
 BtRegistry::BtRegistry()
-  : tcpPort_(0)
+  : tcpPort_(0),
+    udpPort_(0)
 {}
 
 BtRegistry::~BtRegistry() {}
@@ -107,6 +109,12 @@ void BtRegistry::setLpdMessageReceiver
   lpdMessageReceiver_ = receiver;
 }
 
+void BtRegistry::setUDPTrackerClient
+(const SharedHandle<UDPTrackerClient>& tracker)
+{
+  udpTrackerClient_ = tracker;
+}
+
 BtObject::BtObject
 (const SharedHandle<DownloadContext>& downloadContext,
  const SharedHandle<PieceStorage>& pieceStorage,

+ 19 - 0
src/BtRegistry.h

@@ -51,6 +51,7 @@ class BtRuntime;
 class BtProgressInfoFile;
 class DownloadContext;
 class LpdMessageReceiver;
+class UDPTrackerClient;
 
 struct BtObject {
   SharedHandle<DownloadContext> downloadContext;
@@ -80,7 +81,10 @@ class BtRegistry {
 private:
   std::map<a2_gid_t, SharedHandle<BtObject> > pool_;
   uint16_t tcpPort_;
+  // This is IPv4 port for DHT and UDP tracker. No IPv6 udpPort atm.
+  uint16_t udpPort_;
   SharedHandle<LpdMessageReceiver> lpdMessageReceiver_;
+  SharedHandle<UDPTrackerClient> udpTrackerClient_;
 public:
   BtRegistry();
   ~BtRegistry();
@@ -118,11 +122,26 @@ public:
     return tcpPort_;
   }
 
+  void setUdpPort(uint16_t port)
+  {
+    udpPort_ = port;
+  }
+  uint16_t getUdpPort() const
+  {
+    return udpPort_;
+  }
+
   void setLpdMessageReceiver(const SharedHandle<LpdMessageReceiver>& receiver);
   const SharedHandle<LpdMessageReceiver>& getLpdMessageReceiver() const
   {
     return lpdMessageReceiver_;
   }
+
+  void setUDPTrackerClient(const SharedHandle<UDPTrackerClient>& tracker);
+  const SharedHandle<UDPTrackerClient>& getUDPTrackerClient() const
+  {
+    return udpTrackerClient_;
+  }
 };
 
 } // namespace aria2

+ 1 - 0
src/BtSetup.cc

@@ -67,6 +67,7 @@
 #include "DHTMessageReceiver.h"
 #include "DHTMessageFactory.h"
 #include "DHTMessageCallback.h"
+#include "UDPTrackerClient.h"
 #include "BtProgressInfoFile.h"
 #include "BtAnnounce.h"
 #include "BtRuntime.h"

+ 64 - 9
src/DHTInteractionCommand.cc

@@ -46,10 +46,16 @@
 #include "LogFactory.h"
 #include "DHTMessageCallback.h"
 #include "DHTNode.h"
+#include "DHTConnection.h"
+#include "UDPTrackerClient.h"
+#include "UDPTrackerRequest.h"
 #include "fmt.h"
+#include "wallclock.h"
 
 namespace aria2 {
 
+// TODO This name of this command is misleading, because now it also
+// handles UDP trackers as well as DHT.
 DHTInteractionCommand::DHTInteractionCommand(cuid_t cuid, DownloadEngine* e)
   : Command(cuid),
     e_(e)
@@ -77,23 +83,60 @@ void DHTInteractionCommand::disableReadCheckSocket(const SharedHandle<SocketCore
 
 bool DHTInteractionCommand::execute()
 {
-  if(e_->getRequestGroupMan()->downloadFinished() || e_->isHaltRequested()) {
+  // We need to keep this command alive while TrackerWatcherCommand
+  // needs this.
+  if(e_->getRequestGroupMan()->downloadFinished() ||
+     (e_->isHaltRequested() && udpTrackerClient_->getNumWatchers() == 0)) {
+    return true;
+  } else if(e_->isForceHaltRequested()) {
+    udpTrackerClient_->failAll();
     return true;
   }
 
   taskQueue_->executeTask();
 
-  while(1) {
-    SharedHandle<DHTMessage> m = receiver_->receiveMessage();
-    if(!m) {
-      break;
+  std::string remoteAddr;
+  uint16_t remotePort;
+  unsigned char data[64*1024];
+  try {
+    while(1) {
+      ssize_t length = connection_->receiveMessage(data, sizeof(data),
+                                                   remoteAddr, remotePort);
+      if(length <= 0) {
+        break;
+      }
+      if(data[0] == 'd') {
+        // udp tracker response does not start with 'd', so assume
+        // this message belongs to DHT. nothrow.
+        receiver_->receiveMessage(remoteAddr, remotePort, data, length);
+      } else {
+        // this may be udp tracker response. nothrow.
+        udpTrackerClient_->receiveReply(data, length, remoteAddr, remotePort,
+                                        global::wallclock());
+      }
     }
+  } catch(RecoverableException& e) {
+    A2_LOG_INFO_EX("Exception thrown while receiving UDP message.", e);
   }
   receiver_->handleTimeout();
-  try {
-    dispatcher_->sendMessages();
-  } catch(RecoverableException& e) {
-    A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
+  udpTrackerClient_->handleTimeout(global::wallclock());
+  dispatcher_->sendMessages();
+  while(!udpTrackerClient_->getPendingRequests().empty()) {
+    // no throw
+    ssize_t length = udpTrackerClient_->createRequest(data, sizeof(data),
+                                                      remoteAddr, remotePort,
+                                                      global::wallclock());
+    if(length == -1) {
+      break;
+    }
+    try {
+      // throw
+      connection_->sendMessage(data, length, remoteAddr, remotePort);
+      udpTrackerClient_->requestSent(global::wallclock());
+    } catch(RecoverableException& e) {
+      A2_LOG_INFO_EX("Exception thrown while sending UDP tracker request.", e);
+      udpTrackerClient_->requestFail(UDPT_ERR_NETWORK);
+    }
   }
   e_->addCommand(this);
   return false;
@@ -114,4 +157,16 @@ void DHTInteractionCommand::setTaskQueue(const SharedHandle<DHTTaskQueue>& taskQ
   taskQueue_ = taskQueue;
 }
 
+void DHTInteractionCommand::setConnection
+(const SharedHandle<DHTConnection>& connection)
+{
+  connection_ = connection;
+}
+
+void DHTInteractionCommand::setUDPTrackerClient
+(const SharedHandle<UDPTrackerClient>& udpTrackerClient)
+{
+  udpTrackerClient_ = udpTrackerClient;
+}
+
 } // namespace aria2

+ 9 - 0
src/DHTInteractionCommand.h

@@ -45,6 +45,8 @@ class DHTMessageReceiver;
 class DHTTaskQueue;
 class DownloadEngine;
 class SocketCore;
+class DHTConnection;
+class UDPTrackerClient;
 
 class DHTInteractionCommand:public Command {
 private:
@@ -53,6 +55,8 @@ private:
   SharedHandle<DHTMessageReceiver> receiver_;
   SharedHandle<DHTTaskQueue> taskQueue_;
   SharedHandle<SocketCore> readCheckSocket_;
+  SharedHandle<DHTConnection> connection_;
+  SharedHandle<UDPTrackerClient> udpTrackerClient_;
 public:
   DHTInteractionCommand(cuid_t cuid, DownloadEngine* e);
 
@@ -69,6 +73,11 @@ public:
   void setMessageReceiver(const SharedHandle<DHTMessageReceiver>& receiver);
 
   void setTaskQueue(const SharedHandle<DHTTaskQueue>& taskQueue);
+
+  void setConnection(const SharedHandle<DHTConnection>& connection);
+
+  void setUDPTrackerClient
+  (const SharedHandle<UDPTrackerClient>& udpTrackerClient);
 };
 
 } // namespace aria2

+ 8 - 15
src/DHTMessageReceiver.cc

@@ -63,18 +63,11 @@ DHTMessageReceiver::DHTMessageReceiver
 
 DHTMessageReceiver::~DHTMessageReceiver() {}
 
-SharedHandle<DHTMessage> DHTMessageReceiver::receiveMessage()
+SharedHandle<DHTMessage> DHTMessageReceiver::receiveMessage
+(const std::string& remoteAddr, uint16_t remotePort, unsigned char *data,
+ size_t length)
 {
-  std::string remoteAddr;
-  uint16_t remotePort;
-  unsigned char data[64*1024];
   try {
-    ssize_t length = connection_->receiveMessage(data, sizeof(data),
-                                                 remoteAddr,
-                                                 remotePort);
-    if(length <= 0) {
-      return SharedHandle<DHTMessage>();
-    }
     bool isReply = false;
     SharedHandle<ValueBase> decoded = bencode2::decode(data, length);
     const Dict* dict = downcast<Dict>(decoded);
@@ -87,13 +80,13 @@ SharedHandle<DHTMessage> DHTMessageReceiver::receiveMessage()
       } else {
         A2_LOG_INFO(fmt("Malformed DHT message. Missing 'y' key. From:%s:%u",
                         remoteAddr.c_str(), remotePort));
-        return handleUnknownMessage(data, sizeof(data), remoteAddr, remotePort);
+        return handleUnknownMessage(data, length, remoteAddr, remotePort);
       }
     } else {
       A2_LOG_INFO(fmt("Malformed DHT message. This is not a bencoded directory."
                       " From:%s:%u",
                       remoteAddr.c_str(), remotePort));
-      return handleUnknownMessage(data, sizeof(data), remoteAddr, remotePort);
+      return handleUnknownMessage(data, length, remoteAddr, remotePort);
     }
     if(isReply) {
       std::pair<SharedHandle<DHTResponseMessage>,
@@ -101,7 +94,7 @@ SharedHandle<DHTMessage> DHTMessageReceiver::receiveMessage()
         tracker_->messageArrived(dict, remoteAddr, remotePort);
       if(!p.first) {
         // timeout or malicious? message
-        return handleUnknownMessage(data, sizeof(data), remoteAddr, remotePort);
+        return handleUnknownMessage(data, length, remoteAddr, remotePort);
       }
       onMessageReceived(p.first);
       if(p.second) {
@@ -114,14 +107,14 @@ SharedHandle<DHTMessage> DHTMessageReceiver::receiveMessage()
       if(*message->getLocalNode() == *message->getRemoteNode()) {
         // drop message from localnode
         A2_LOG_INFO("Received DHT message from localnode.");
-        return handleUnknownMessage(data, sizeof(data), remoteAddr, remotePort);
+        return handleUnknownMessage(data, length, remoteAddr, remotePort);
       }
       onMessageReceived(message);
       return message;
     }
   } catch(RecoverableException& e) {
     A2_LOG_INFO_EX("Exception thrown while receiving DHT message.", e);
-    return handleUnknownMessage(data, sizeof(data), remoteAddr, remotePort);
+    return handleUnknownMessage(data, length, remoteAddr, remotePort);
   }
 }
 

+ 3 - 1
src/DHTMessageReceiver.h

@@ -69,7 +69,9 @@ public:
 
   ~DHTMessageReceiver();
 
-  SharedHandle<DHTMessage> receiveMessage();
+  SharedHandle<DHTMessage> receiveMessage
+  (const std::string& remoteAddr, uint16_t remotePort, unsigned char *data,
+   size_t length);
 
   void handleTimeout();
 

+ 13 - 2
src/DHTSetup.cc

@@ -61,6 +61,8 @@
 #include "DHTRegistry.h"
 #include "DHTBucketRefreshTask.h"
 #include "DHTMessageCallback.h"
+#include "UDPTrackerClient.h"
+#include "BtRegistry.h"
 #include "prefs.h"
 #include "Option.h"
 #include "SocketCore.h"
@@ -176,6 +178,8 @@ void DHTSetup::setup
     factory->setTokenTracker(tokenTracker.get());
     factory->setLocalNode(localNode);
 
+    // For now, UDPTrackerClient was enabled along with DHT
+    SharedHandle<UDPTrackerClient> udpTrackerClient(new UDPTrackerClient());
     // assign them into DHTRegistry
     if(family == AF_INET) {
       DHTRegistry::getMutableData().localNode = localNode;
@@ -187,6 +191,8 @@ void DHTSetup::setup
       DHTRegistry::getMutableData().messageDispatcher = dispatcher;
       DHTRegistry::getMutableData().messageReceiver = receiver;
       DHTRegistry::getMutableData().messageFactory = factory;
+      e->getBtRegistry()->setUDPTrackerClient(udpTrackerClient);
+      e->getBtRegistry()->setUdpPort(localNode->getPort());
     } else {
       DHTRegistry::getMutableData6().localNode = localNode;
       DHTRegistry::getMutableData6().routingTable = routingTable;
@@ -244,6 +250,8 @@ void DHTSetup::setup
       command->setMessageReceiver(receiver);
       command->setTaskQueue(taskQueue);
       command->setReadCheckSocket(connection->getSocket());
+      command->setConnection(connection);
+      command->setUDPTrackerClient(udpTrackerClient);
       tempCommands->push_back(command);
     }
     {
@@ -282,12 +290,15 @@ void DHTSetup::setup
     }
     commands.insert(commands.end(), tempCommands->begin(), tempCommands->end());
     tempCommands->clear();
-  } catch(RecoverableException& e) {
+  } catch(RecoverableException& ex) {
     A2_LOG_ERROR_EX(fmt("Exception caught while initializing DHT functionality."
                         " DHT is disabled."),
-                    e);
+                    ex);
     if(family == AF_INET) {
       DHTRegistry::clearData();
+      e->getBtRegistry()->setUDPTrackerClient
+        (SharedHandle<UDPTrackerClient>());
+      e->getBtRegistry()->setUdpPort(0);
     } else {
       DHTRegistry::clearData6();
     }

+ 88 - 1
src/DefaultBtAnnounce.cc

@@ -52,6 +52,8 @@
 #include "bittorrent_helper.h"
 #include "wallclock.h"
 #include "uri.h"
+#include "UDPTrackerRequest.h"
+#include "SocketCore.h"
 
 namespace aria2 {
 
@@ -115,7 +117,7 @@ bool uriHasQuery(const std::string& uri)
 }
 } // namespace
 
-std::string DefaultBtAnnounce::getAnnounceUrl() {
+bool DefaultBtAnnounce::adjustAnnounceList() {
   if(isStoppedAnnounceReady()) {
     if(!announceList_.currentTierAcceptsStoppedEvent()) {
       announceList_.moveToStoppedAllowedTier();
@@ -135,6 +137,13 @@ std::string DefaultBtAnnounce::getAnnounceUrl() {
       announceList_.setEvent(AnnounceTier::STARTED_AFTER_COMPLETION);
     }
   } else {
+    return false;
+  }
+  return true;
+}
+
+std::string DefaultBtAnnounce::getAnnounceUrl() {
+  if(!adjustAnnounceList()) {
     return A2STR::NIL;
   }
   int numWant = 50;
@@ -193,6 +202,60 @@ std::string DefaultBtAnnounce::getAnnounceUrl() {
   return uri;
 }
 
+SharedHandle<UDPTrackerRequest> DefaultBtAnnounce::createUDPTrackerRequest
+(const std::string& remoteAddr, uint16_t remotePort, uint16_t localPort)
+{
+  if(!adjustAnnounceList()) {
+    return SharedHandle<UDPTrackerRequest>();
+  }
+  NetStat& stat = downloadContext_->getNetStat();
+  int64_t left =
+    pieceStorage_->getTotalLength()-pieceStorage_->getCompletedLength();
+  SharedHandle<UDPTrackerRequest> req(new UDPTrackerRequest());
+  req->remoteAddr = remoteAddr;
+  req->remotePort = remotePort;
+  req->action = UDPT_ACT_ANNOUNCE;
+  req->infohash = bittorrent::getTorrentAttrs(downloadContext_)->infoHash;
+  const unsigned char* peerId = bittorrent::getStaticPeerId();
+  req->peerId.assign(peerId, peerId + PEER_ID_LENGTH);
+  req->downloaded = stat.getSessionDownloadLength();
+  req->left = left;
+  req->uploaded = stat.getSessionUploadLength();
+  switch(announceList_.getEvent()) {
+  case AnnounceTier::STARTED:
+  case AnnounceTier::STARTED_AFTER_COMPLETION:
+    req->event = UDPT_EVT_STARTED;
+    break;
+  case AnnounceTier::STOPPED:
+    req->event = UDPT_EVT_STOPPED;
+    break;
+  case AnnounceTier::COMPLETED:
+    req->event = UDPT_EVT_COMPLETED;
+    break;
+  default:
+    req->event = 0;
+  }
+  if(!option_->blank(PREF_BT_EXTERNAL_IP)) {
+    unsigned char dest[16];
+    if(net::getBinAddr(dest, option_->get(PREF_BT_EXTERNAL_IP)) == 4) {
+      memcpy(&req->ip, dest, 4);
+    } else {
+      req->ip = 0;
+    }
+  } else {
+    req->ip = 0;
+  }
+  req->key = randomizer_->getRandomNumber(INT32_MAX);
+  int numWant = 50;
+  if(!btRuntime_->lessThanMinPeers() || btRuntime_->isHalt()) {
+    numWant = 0;
+  }
+  req->numWant = numWant;
+  req->port = localPort;
+  req->extensions = 0;
+  return req;
+}
+
 void DefaultBtAnnounce::announceStart() {
   ++trackers_;
 }
@@ -287,6 +350,30 @@ DefaultBtAnnounce::processAnnounceResponse(const unsigned char* trackerResponse,
   }
 }
 
+void DefaultBtAnnounce::processUDPTrackerResponse
+(const SharedHandle<UDPTrackerRequest>& req)
+{
+  const SharedHandle<UDPTrackerReply>& reply = req->reply;
+  A2_LOG_DEBUG("Now processing UDP tracker response.");
+  if(reply->interval > 0) {
+    minInterval_ = reply->interval;
+    A2_LOG_DEBUG(fmt("Min interval:%ld", static_cast<long int>(minInterval_)));
+    interval_ = minInterval_;
+  }
+  complete_ = reply->seeders;
+  A2_LOG_DEBUG(fmt("Complete:%d", reply->seeders));
+  incomplete_ = reply->leechers;
+  A2_LOG_DEBUG(fmt("Incomplete:%d", reply->leechers));
+  if(!btRuntime_->isHalt() && btRuntime_->lessThanMinPeers()) {
+    for(std::vector<std::pair<std::string, uint16_t> >::iterator i =
+          reply->peers.begin(), eoi = reply->peers.end(); i != eoi;
+        ++i) {
+      peerStorage_->addPeer(SharedHandle<Peer>(new Peer((*i).first,
+                                                        (*i).second)));
+    }
+  }
+}
+
 bool DefaultBtAnnounce::noMoreAnnounce() {
   return (trackers_ == 0 &&
           btRuntime_->isHalt() &&

+ 9 - 0
src/DefaultBtAnnounce.h

@@ -66,6 +66,8 @@ private:
   SharedHandle<PieceStorage> pieceStorage_;
   SharedHandle<PeerStorage> peerStorage_;
   uint16_t tcpPort_;
+
+  bool adjustAnnounceList();
 public:
   DefaultBtAnnounce(const SharedHandle<DownloadContext>& downloadContext,
                     const Option* option);
@@ -103,6 +105,10 @@ public:
 
   virtual std::string getAnnounceUrl();
 
+  virtual SharedHandle<UDPTrackerRequest>
+  createUDPTrackerRequest(const std::string& remoteAddr, uint16_t remotePort,
+                          uint16_t localPort);
+
   virtual void announceStart();
 
   virtual void announceSuccess();
@@ -116,6 +122,9 @@ public:
   virtual void processAnnounceResponse(const unsigned char* trackerResponse,
                                        size_t trackerResponseLength);
 
+  virtual void processUDPTrackerResponse
+  (const SharedHandle<UDPTrackerRequest>& req);
+
   virtual bool noMoreAnnounce();
 
   virtual void shuffleAnnounce();

+ 3 - 3
src/DownloadEngine.cc

@@ -85,7 +85,7 @@ volatile sig_atomic_t globalHaltRequested = 0;
 
 DownloadEngine::DownloadEngine(const SharedHandle<EventPoll>& eventPoll)
   : eventPoll_(eventPoll),
-    haltRequested_(false),
+    haltRequested_(0),
     noWait_(false),
     refreshInterval_(DEFAULT_REFRESH_INTERVAL),
     cookieStorage_(new CookieStorage()),
@@ -239,13 +239,13 @@ void DownloadEngine::afterEachIteration()
 
 void DownloadEngine::requestHalt()
 {
-  haltRequested_ = true;
+  haltRequested_ = std::max(haltRequested_, 1);
   requestGroupMan_->halt();
 }
 
 void DownloadEngine::requestForceHalt()
 {
-  haltRequested_ = true;
+  haltRequested_ = std::max(haltRequested_, 2);
   requestGroupMan_->forceHalt();
 }
 

+ 6 - 1
src/DownloadEngine.h

@@ -79,7 +79,7 @@ private:
 
   SharedHandle<StatCalc> statCalc_;
 
-  bool haltRequested_;
+  int haltRequested_;
 
   class SocketPoolEntry {
   private:
@@ -230,6 +230,11 @@ public:
     return haltRequested_;
   }
 
+  bool isForceHaltRequested() const
+  {
+    return haltRequested_ >= 2;
+  }
+
   void requestHalt();
 
   void requestForceHalt();

+ 4 - 1
src/Makefile.am

@@ -522,7 +522,10 @@ SRCS += PeerAbstractCommand.cc PeerAbstractCommand.h\
 	ValueBaseBencodeParser.h\
 	BencodeDiskWriter.h\
 	BencodeDiskWriterFactory.h\
-	MemoryBencodePreDownloadHandler.h
+	MemoryBencodePreDownloadHandler.h\
+	UDPTrackerClient.cc UDPTrackerClient.h\
+	UDPTrackerRequest.cc UDPTrackerRequest.h\
+	NameResolveCommand.cc NameResolveCommand.h
 endif # ENABLE_BITTORRENT
 
 if ENABLE_METALINK

+ 193 - 0
src/NameResolveCommand.cc

@@ -0,0 +1,193 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 Tatsuhiro Tsujikawa
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "NameResolveCommand.h"
+#include "DownloadEngine.h"
+#include "NameResolver.h"
+#include "prefs.h"
+#include "message.h"
+#include "util.h"
+#include "Option.h"
+#include "RequestGroupMan.h"
+#include "Logger.h"
+#include "LogFactory.h"
+#include "fmt.h"
+#include "UDPTrackerRequest.h"
+#include "UDPTrackerClient.h"
+#include "BtRegistry.h"
+#ifdef ENABLE_ASYNC_DNS
+#include "AsyncNameResolver.h"
+#endif // ENABLE_ASYNC_DNS
+
+namespace aria2 {
+
+NameResolveCommand::NameResolveCommand
+(cuid_t cuid, DownloadEngine* e,
+ const SharedHandle<UDPTrackerRequest>& req)
+  : Command(cuid),
+    e_(e),
+    req_(req)
+{
+  setStatus(Command::STATUS_ONESHOT_REALTIME);
+}
+
+NameResolveCommand::~NameResolveCommand()
+{
+#ifdef ENABLE_ASYNC_DNS
+  disableNameResolverCheck(resolver_);
+#endif // ENABLE_ASYNC_DNS
+}
+
+bool NameResolveCommand::execute()
+{
+  // This is UDP tracker specific, but we need to keep this command
+  // alive until force shutdown is
+  // commencing. RequestGroupMan::downloadFinished() is useless here
+  // at the moment.
+  if(e_->isForceHaltRequested()) {
+    onShutdown();
+    return true;
+  }
+#ifdef ENABLE_ASYNC_DNS
+  if(!resolver_) {
+    int family = AF_INET;
+    resolver_.reset(new AsyncNameResolver(family
+#ifdef HAVE_ARES_ADDR_NODE
+                                          , e_->getAsyncDNSServers()
+#endif // HAVE_ARES_ADDR_NODE
+                                          ));
+  }
+#endif // ENABLE_ASYNC_DNS
+  std::string hostname = req_->remoteAddr;
+  std::vector<std::string> res;
+  if(util::isNumericHost(hostname)) {
+    res.push_back(hostname);
+  } else {
+#ifdef ENABLE_ASYNC_DNS
+    if(e_->getOption()->getAsBool(PREF_ASYNC_DNS)) {
+      try {
+        if(resolveHostname(hostname, resolver_)) {
+          res = resolver_->getResolvedAddresses();
+        } else {
+          e_->addCommand(this);
+          return false;
+        }
+      } catch(RecoverableException& e) {
+        A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
+      }
+    } else
+#endif // ENABLE_ASYNC_DNS
+      {
+        NameResolver resolver;
+        resolver.setSocktype(SOCK_DGRAM);
+        try {
+          resolver.resolve(res, hostname);
+        } catch(RecoverableException& e) {
+          A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
+        }
+      }
+  }
+  if(res.empty()) {
+    onFailure();
+  } else {
+    onSuccess(res, e_);
+  }
+  return true;
+}
+
+void NameResolveCommand::onShutdown()
+{
+  req_->state = UDPT_STA_COMPLETE;
+  req_->error = UDPT_ERR_SHUTDOWN;
+}
+
+void NameResolveCommand::onFailure()
+{
+  req_->state = UDPT_STA_COMPLETE;
+  req_->error = UDPT_ERR_NETWORK;
+}
+
+void NameResolveCommand::onSuccess
+(const std::vector<std::string>& addrs, DownloadEngine* e)
+{
+  req_->remoteAddr = addrs[0];
+  e->getBtRegistry()->getUDPTrackerClient()->addRequest(req_);
+}
+
+#ifdef ENABLE_ASYNC_DNS
+
+bool NameResolveCommand::resolveHostname
+(const std::string& hostname,
+ const SharedHandle<AsyncNameResolver>& resolver)
+{
+  switch(resolver->getStatus()) {
+  case AsyncNameResolver::STATUS_READY:
+      A2_LOG_INFO(fmt(MSG_RESOLVING_HOSTNAME,
+                      getCuid(),
+                      hostname.c_str()));
+    resolver->resolve(hostname);
+    setNameResolverCheck(resolver);
+    return false;
+  case AsyncNameResolver::STATUS_SUCCESS:
+    A2_LOG_INFO(fmt(MSG_NAME_RESOLUTION_COMPLETE,
+                    getCuid(),
+                    resolver->getHostname().c_str(),
+                    resolver->getResolvedAddresses().front().c_str()));
+    return true;
+    break;
+  case AsyncNameResolver::STATUS_ERROR:
+    throw DL_ABORT_EX
+      (fmt(MSG_NAME_RESOLUTION_FAILED,
+           getCuid(),
+           hostname.c_str(),
+           resolver->getError().c_str()));
+  default:
+    return false;
+  }
+}
+
+void NameResolveCommand::setNameResolverCheck
+(const SharedHandle<AsyncNameResolver>& resolver)
+{
+  e_->addNameResolverCheck(resolver, this);
+}
+
+void NameResolveCommand::disableNameResolverCheck
+(const SharedHandle<AsyncNameResolver>& resolver)
+{
+  e_->deleteNameResolverCheck(resolver, this);
+}
+#endif // ENABLE_ASYNC_DNS
+
+} // namespace aria2

+ 87 - 0
src/NameResolveCommand.h

@@ -0,0 +1,87 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 Tatsuhiro Tsujikawa
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#ifndef D_NAME_RESOLVE_COMMAND_H
+#define D_NAME_RESOLVE_COMMAND_H
+
+#include "Command.h"
+
+#include <string>
+#include <vector>
+
+#include "SharedHandle.h"
+
+// TODO Make this class generic.
+namespace aria2 {
+
+class DownloadEngine;
+#ifdef ENABLE_ASYNC_DNS
+class AsyncNameResolver;
+#endif // ENABLE_ASYNC_DNS
+class UDPTrackerRequest;
+
+class NameResolveCommand:public Command {
+private:
+  DownloadEngine* e_;
+
+#ifdef ENABLE_ASYNC_DNS
+  SharedHandle<AsyncNameResolver> resolver_;
+#endif // ENABLE_ASYNC_DNS
+
+#ifdef ENABLE_ASYNC_DNS
+  bool resolveHostname(const std::string& hostname,
+                       const SharedHandle<AsyncNameResolver>& resolver);
+
+  void setNameResolverCheck(const SharedHandle<AsyncNameResolver>& resolver);
+
+  void disableNameResolverCheck(const SharedHandle<AsyncNameResolver>& resolver);
+#endif // ENABLE_ASYNC_DNS
+
+  SharedHandle<UDPTrackerRequest> req_;
+  void onShutdown();
+  void onFailure();
+  void onSuccess
+  (const std::vector<std::string>& addrs, DownloadEngine* e);
+public:
+  NameResolveCommand(cuid_t cuid, DownloadEngine* e,
+                     const SharedHandle<UDPTrackerRequest>& req);
+
+  virtual ~NameResolveCommand();
+
+  virtual bool execute();
+};
+
+} // namespace aria2
+
+#endif // D_NAME_RESOVE_COMMAND_H

+ 187 - 62
src/TrackerWatcherCommand.cc

@@ -63,32 +63,156 @@
 #include "a2functional.h"
 #include "util.h"
 #include "fmt.h"
+#include "UDPTrackerRequest.h"
+#include "UDPTrackerClient.h"
+#include "BtRegistry.h"
+#include "NameResolveCommand.h"
 
 namespace aria2 {
 
+HTTPAnnRequest::HTTPAnnRequest(const SharedHandle<RequestGroup>& rg)
+  : rg_(rg)
+{}
+
+HTTPAnnRequest::~HTTPAnnRequest()
+{}
+
+bool HTTPAnnRequest::stopped() const
+{
+  return rg_->getNumCommand() == 0;
+}
+
+bool HTTPAnnRequest::success() const
+{
+  return rg_->downloadFinished();
+}
+
+void HTTPAnnRequest::stop(DownloadEngine* e)
+{
+  rg_->setForceHaltRequested(true);
+}
+
+bool HTTPAnnRequest::issue(DownloadEngine* e)
+{
+  try {
+    std::vector<Command*>* commands = new std::vector<Command*>();
+    auto_delete_container<std::vector<Command*> > commandsDel(commands);
+    rg_->createInitialCommand(*commands, e);
+    e->addCommand(*commands);
+    e->setNoWait(true);
+    commands->clear();
+    A2_LOG_DEBUG("added tracker request command");
+    return true;
+  } catch(RecoverableException& ex) {
+    A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, ex);
+    return false;
+  }
+}
+
+bool HTTPAnnRequest::processResponse
+(const SharedHandle<BtAnnounce>& btAnnounce)
+{
+  try {
+    std::stringstream strm;
+    unsigned char data[2048];
+    rg_->getPieceStorage()->getDiskAdaptor()->openFile();
+    while(1) {
+      ssize_t dataLength = rg_->getPieceStorage()->
+        getDiskAdaptor()->readData(data, sizeof(data), strm.tellp());
+      if(dataLength == 0) {
+        break;
+      }
+      strm.write(reinterpret_cast<const char*>(data), dataLength);
+    }
+    std::string res = strm.str();
+    btAnnounce->processAnnounceResponse
+      (reinterpret_cast<const unsigned char*>(res.c_str()), res.size());
+    return true;
+  } catch(RecoverableException& e) {
+    A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
+    return false;
+  }
+}
+
+UDPAnnRequest::UDPAnnRequest(const SharedHandle<UDPTrackerRequest>& req)
+  : req_(req)
+{}
+
+UDPAnnRequest::~UDPAnnRequest()
+{}
+
+bool UDPAnnRequest::stopped() const
+{
+  return !req_ || req_->state == UDPT_STA_COMPLETE;
+}
+
+bool UDPAnnRequest::success() const
+{
+  return req_ && req_->state == UDPT_STA_COMPLETE &&
+    req_->error == UDPT_ERR_SUCCESS;
+}
+
+void UDPAnnRequest::stop(DownloadEngine* e)
+{
+  if(req_) {
+    req_.reset();
+  }
+}
+
+bool UDPAnnRequest::issue(DownloadEngine* e)
+{
+  if(req_) {
+    NameResolveCommand* command = new NameResolveCommand
+      (e->newCUID(), e, req_);
+    e->addCommand(command);
+    e->setNoWait(true);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+bool UDPAnnRequest::processResponse
+(const SharedHandle<BtAnnounce>& btAnnounce)
+{
+  if(req_) {
+    btAnnounce->processUDPTrackerResponse(req_);
+    return true;
+  } else {
+    return false;
+  }
+}
+
 TrackerWatcherCommand::TrackerWatcherCommand
 (cuid_t cuid, RequestGroup* requestGroup, DownloadEngine* e)
   : Command(cuid),
     requestGroup_(requestGroup),
-    e_(e)
+    e_(e),
+    udpTrackerClient_(e_->getBtRegistry()->getUDPTrackerClient())
 {
   requestGroup_->increaseNumCommand();
+  if(udpTrackerClient_) {
+    udpTrackerClient_->increaseWatchers();
+  }
 }
 
 TrackerWatcherCommand::~TrackerWatcherCommand()
 {
   requestGroup_->decreaseNumCommand();
+  if(udpTrackerClient_) {
+    udpTrackerClient_->decreaseWatchers();
+  }
 }
 
 bool TrackerWatcherCommand::execute() {
   if(requestGroup_->isForceHaltRequested()) {
-    if(!trackerRequestGroup_) {
+    if(!trackerRequest_) {
       return true;
-    } else if(trackerRequestGroup_->getNumCommand() == 0 ||
-              trackerRequestGroup_->downloadFinished()) {
+    } else if(trackerRequest_->stopped() ||
+              trackerRequest_->success()) {
       return true;
     } else {
-      trackerRequestGroup_->setForceHaltRequested(true);
+      trackerRequest_->stop(e_);
       e_->setRefreshInterval(0);
       e_->addCommand(this);
       return false;
@@ -98,44 +222,32 @@ bool TrackerWatcherCommand::execute() {
     A2_LOG_DEBUG("no more announce");
     return true;
   }
-  if(!trackerRequestGroup_) {
-    trackerRequestGroup_ = createAnnounce();
-    if(trackerRequestGroup_) {
-      try {
-        std::vector<Command*>* commands = new std::vector<Command*>();
-        auto_delete_container<std::vector<Command*> > commandsDel(commands);
-        trackerRequestGroup_->createInitialCommand(*commands, e_);
-        e_->addCommand(*commands);
-        commands->clear();
-        A2_LOG_DEBUG("added tracker request command");
-      } catch(RecoverableException& ex) {
-        A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, ex);
-      }
+  if(!trackerRequest_) {
+    trackerRequest_ = createAnnounce(e_);
+    if(trackerRequest_) {
+      trackerRequest_->issue(e_);
     }
-  } else if(trackerRequestGroup_->getNumCommand() == 0) {
+  } else if(trackerRequest_->stopped()) {
     // We really want to make sure that tracker request has finished
     // by checking getNumCommand() == 0. Because we reset
     // trackerRequestGroup_, if it is still used in other Command, we
     // will get Segmentation fault.
-    if(trackerRequestGroup_->downloadFinished()) {
-      try {
-        std::string trackerResponse = getTrackerResponse(trackerRequestGroup_);
-
-        processTrackerResponse(trackerResponse);
+    if(trackerRequest_->success()) {
+      if(trackerRequest_->processResponse(btAnnounce_)) {
         btAnnounce_->announceSuccess();
         btAnnounce_->resetAnnounce();
-      } catch(RecoverableException& ex) {
-        A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, ex);
+        addConnection();
+      } else {
         btAnnounce_->announceFailure();
         if(btAnnounce_->isAllAnnounceFailed()) {
           btAnnounce_->resetAnnounce();
         }
       }
-      trackerRequestGroup_.reset();
+      trackerRequest_.reset();
     } else {
       // handle errors here
       btAnnounce_->announceFailure(); // inside it, trackers = 0.
-      trackerRequestGroup_.reset();
+      trackerRequest_.reset();
       if(btAnnounce_->isAllAnnounceFailed()) {
         btAnnounce_->resetAnnounce();
       }
@@ -145,30 +257,8 @@ bool TrackerWatcherCommand::execute() {
   return false;
 }
 
-std::string TrackerWatcherCommand::getTrackerResponse
-(const SharedHandle<RequestGroup>& requestGroup)
-{
-  std::stringstream strm;
-  unsigned char data[2048];
-  requestGroup->getPieceStorage()->getDiskAdaptor()->openFile();
-  while(1) {
-    ssize_t dataLength = requestGroup->getPieceStorage()->
-      getDiskAdaptor()->readData(data, sizeof(data), strm.tellp());
-    if(dataLength == 0) {
-      break;
-    }
-    strm.write(reinterpret_cast<const char*>(data), dataLength);
-  }
-  return strm.str();
-}
-
-// TODO we have to deal with the exception thrown By BtAnnounce
-void TrackerWatcherCommand::processTrackerResponse
-(const std::string& trackerResponse)
+void TrackerWatcherCommand::addConnection()
 {
-  btAnnounce_->processAnnounceResponse
-    (reinterpret_cast<const unsigned char*>(trackerResponse.c_str()),
-     trackerResponse.size());
   while(!btRuntime_->isHalt() && btRuntime_->lessThanMinPeers()) {
     if(!peerStorage_->isPeerAvailable()) {
       break;
@@ -180,8 +270,8 @@ void TrackerWatcherCommand::processTrackerResponse
       break;
     }
     PeerInitiateConnectionCommand* command;
-    command = new PeerInitiateConnectionCommand(ncuid, requestGroup_, peer, e_,
-                                                btRuntime_);
+    command = new PeerInitiateConnectionCommand(ncuid, requestGroup_, peer,
+                                                e_, btRuntime_);
     command->setPeerStorage(peerStorage_);
     command->setPieceStorage(pieceStorage_);
     e_->addCommand(command);
@@ -190,13 +280,48 @@ void TrackerWatcherCommand::processTrackerResponse
   }
 }
 
-SharedHandle<RequestGroup> TrackerWatcherCommand::createAnnounce() {
-  SharedHandle<RequestGroup> rg;
-  if(btAnnounce_->isAnnounceReady()) {
-    rg = createRequestGroup(btAnnounce_->getAnnounceUrl());
-    btAnnounce_->announceStart(); // inside it, trackers++.
+SharedHandle<AnnRequest>
+TrackerWatcherCommand::createAnnounce(DownloadEngine* e)
+{
+  SharedHandle<AnnRequest> treq;
+  while(!btAnnounce_->isAllAnnounceFailed() &&
+        btAnnounce_->isAnnounceReady()) {
+    std::string uri = btAnnounce_->getAnnounceUrl();
+    uri_split_result res;
+    memset(&res, 0, sizeof(res));
+    if(uri_split(&res, uri.c_str()) == 0) {
+      // Without UDP tracker support, send it to normal tracker flow
+      // and make it fail.
+      if(udpTrackerClient_ &&
+         uri::getFieldString(res, USR_SCHEME, uri.c_str()) == "udp") {
+        uint16_t localPort;
+        localPort = e->getBtRegistry()->getUdpPort();
+        treq = createUDPAnnRequest
+          (uri::getFieldString(res, USR_HOST, uri.c_str()), res.port,
+           localPort);
+      } else {
+        treq = createHTTPAnnRequest(btAnnounce_->getAnnounceUrl());
+      }
+      btAnnounce_->announceStart(); // inside it, trackers++.
+      break;
+    } else {
+      btAnnounce_->announceFailure();
+    }
   }
-  return rg;
+  if(btAnnounce_->isAllAnnounceFailed()) {
+    btAnnounce_->resetAnnounce();
+  }
+  return treq;
+}
+
+SharedHandle<AnnRequest>
+TrackerWatcherCommand::createUDPAnnRequest(const std::string& host,
+                                           uint16_t port,
+                                           uint16_t localPort)
+{
+  SharedHandle<UDPTrackerRequest> req =
+    btAnnounce_->createUDPTrackerRequest(host, port, localPort);
+  return SharedHandle<AnnRequest>(new UDPAnnRequest(req));
 }
 
 namespace {
@@ -219,8 +344,8 @@ bool backupTrackerIsAvailable
 }
 } // namespace
 
-SharedHandle<RequestGroup>
-TrackerWatcherCommand::createRequestGroup(const std::string& uri)
+SharedHandle<AnnRequest>
+TrackerWatcherCommand::createHTTPAnnRequest(const std::string& uri)
 {
   std::vector<std::string> uris;
   uris.push_back(uri);
@@ -261,7 +386,7 @@ TrackerWatcherCommand::createRequestGroup(const std::string& uri)
   dctx->setAcceptMetalink(false);
   A2_LOG_INFO(fmt("Creating tracker request group GID#%s",
                   GroupId::toHex(rg->getGID()).c_str()));
-  return rg;
+  return SharedHandle<AnnRequest>(new HTTPAnnRequest(rg));
 }
 
 void TrackerWatcherCommand::setBtRuntime

+ 55 - 5
src/TrackerWatcherCommand.h

@@ -50,6 +50,50 @@ class PieceStorage;
 class BtRuntime;
 class BtAnnounce;
 class Option;
+class UDPTrackerRequest;
+class UDPTrackerClient;
+
+class AnnRequest {
+public:
+  virtual ~AnnRequest() {}
+  // Returns true if tracker request is finished, regardless of the
+  // outcome.
+  virtual bool stopped() const = 0;
+  // Returns true if tracker request is successful.
+  virtual bool success() const = 0;
+  // Returns true if issuing request is successful.
+  virtual bool issue(DownloadEngine* e) = 0;
+  // Stop this request.
+  virtual void stop(DownloadEngine* e) = 0;
+  // Returns true if processing tracker response is successful.
+  virtual bool processResponse(const SharedHandle<BtAnnounce>& btAnnounce) = 0;
+};
+
+class HTTPAnnRequest:public AnnRequest {
+public:
+  HTTPAnnRequest(const SharedHandle<RequestGroup>& rg);
+  virtual ~HTTPAnnRequest();
+  virtual bool stopped() const;
+  virtual bool success() const;
+  virtual bool issue(DownloadEngine* e);
+  virtual void stop(DownloadEngine* e);
+  virtual bool processResponse(const SharedHandle<BtAnnounce>& btAnnounce);
+private:
+  SharedHandle<RequestGroup> rg_;
+};
+
+class UDPAnnRequest:public AnnRequest {
+public:
+  UDPAnnRequest(const SharedHandle<UDPTrackerRequest>& req);
+  virtual ~UDPAnnRequest();
+  virtual bool stopped() const;
+  virtual bool success() const;
+  virtual bool issue(DownloadEngine* e);
+  virtual void stop(DownloadEngine* e);
+  virtual bool processResponse(const SharedHandle<BtAnnounce>& btAnnounce);
+private:
+  SharedHandle<UDPTrackerRequest> req_;
+};
 
 class TrackerWatcherCommand : public Command
 {
@@ -58,6 +102,8 @@ private:
 
   DownloadEngine* e_;
 
+  SharedHandle<UDPTrackerClient> udpTrackerClient_;
+
   SharedHandle<PeerStorage> peerStorage_;
 
   SharedHandle<PieceStorage> pieceStorage_;
@@ -66,16 +112,20 @@ private:
 
   SharedHandle<BtAnnounce> btAnnounce_;
 
-  SharedHandle<RequestGroup> trackerRequestGroup_;
+  SharedHandle<AnnRequest> trackerRequest_;
+
   /**
    * Returns a command for announce request. Returns 0 if no announce request
    * is needed.
    */
-  SharedHandle<RequestGroup> createRequestGroup(const std::string& url);
+  SharedHandle<AnnRequest>
+  createHTTPAnnRequest(const std::string& uri);
 
-  std::string getTrackerResponse(const SharedHandle<RequestGroup>& requestGroup);
+  SharedHandle<AnnRequest>
+  createUDPAnnRequest(const std::string& host, uint16_t port,
+                      uint16_t localPort);
 
-  void processTrackerResponse(const std::string& response);
+  void addConnection();
 
   const SharedHandle<Option>& getOption() const;
 public:
@@ -85,7 +135,7 @@ public:
 
   virtual ~TrackerWatcherCommand();
 
-  SharedHandle<RequestGroup> createAnnounce();
+  SharedHandle<AnnRequest> createAnnounce(DownloadEngine* e);
 
   virtual bool execute();
 

+ 616 - 0
src/UDPTrackerClient.cc

@@ -0,0 +1,616 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 Tatsuhiro Tsujikawa
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "UDPTrackerClient.h"
+
+#include "UDPTrackerRequest.h"
+#include "bittorrent_helper.h"
+#include "util.h"
+#include "LogFactory.h"
+#include "SimpleRandomizer.h"
+#include "fmt.h"
+
+namespace aria2 {
+
+UDPTrackerClient::UDPTrackerClient()
+{}
+
+namespace {
+template<typename InputIterator>
+void failRequest(InputIterator first, InputIterator last, int error)
+{
+  for(; first != last; ++first) {
+    (*first)->state = UDPT_STA_COMPLETE;
+    (*first)->error = error;
+  }
+}
+} // namespace
+
+namespace {
+int32_t generateTransactionId()
+{
+  return SimpleRandomizer::getInstance()->getRandomNumber(INT32_MAX);
+}
+} // namespace
+
+namespace {
+void logInvalidLength(const std::string& remoteAddr, uint16_t remotePort,
+                      int action, unsigned long expected, unsigned long actual)
+{
+  A2_LOG_INFO(fmt("UDPT received %s reply from %s:%u invalid length "
+                  "expected:%lu, actual:%lu",
+                  getUDPTrackerActionStr(action),
+                  remoteAddr.c_str(), remotePort, expected, actual));
+}
+} // namespace
+
+namespace {
+void logInvalidTransaction(const std::string& remoteAddr, uint16_t remotePort,
+                           int action, int32_t transactionId)
+{
+  A2_LOG_INFO(fmt("UDPT received %s reply from %s:%u invalid transaction_id=%d",
+                  getUDPTrackerActionStr(action),
+                  remoteAddr.c_str(), remotePort, transactionId));
+}
+} // namespace
+
+namespace {
+void logTooShortLength(const std::string& remoteAddr, uint16_t remotePort,
+                       int action,
+                       unsigned long minLength, unsigned long actual)
+{
+  A2_LOG_INFO(fmt("UDPT received %s reply from %s:%u length too short "
+                  "min:%lu, actual:%lu",
+                  getUDPTrackerActionStr(action),
+                  remoteAddr.c_str(), remotePort, minLength, actual));
+}
+} // namespace
+
+UDPTrackerClient::~UDPTrackerClient()
+{
+  // Make all contained requests fail
+  int error = UDPT_ERR_SHUTDOWN;
+  failRequest(inflightRequests_.begin(), inflightRequests_.end(), error);
+  failRequest(pendingRequests_.begin(), pendingRequests_.end(), error);
+  failRequest(connectRequests_.begin(), connectRequests_.end(), error);
+}
+
+namespace {
+struct CollectAddrPortMatch {
+  bool operator()(const SharedHandle<UDPTrackerRequest>& req) const
+  {
+    if(req->remoteAddr == remoteAddr && req->remotePort == remotePort) {
+      dest.push_back(req);
+      return true;
+    } else {
+      return false;
+    }
+  }
+  std::vector<SharedHandle<UDPTrackerRequest> >& dest;
+  std::string remoteAddr;
+  uint16_t remotePort;
+  CollectAddrPortMatch(std::vector<SharedHandle<UDPTrackerRequest> >& dest,
+                       const std::string& remoteAddr, uint16_t remotePort)
+    : dest(dest), remoteAddr(remoteAddr), remotePort(remotePort)
+  {}
+};
+} // namespace
+
+int UDPTrackerClient::receiveReply
+(const unsigned char* data, size_t length, const std::string& remoteAddr,
+ uint16_t remotePort, const Timer& now)
+{
+  int32_t action = bittorrent::getIntParam(data, 0);
+  switch(action) {
+  case UDPT_ACT_CONNECT: {
+    if(length != 16) {
+      logInvalidLength(remoteAddr, remotePort, action, 16, length);
+      return -1;
+    }
+    int32_t transactionId = bittorrent::getIntParam(data, 4);
+    SharedHandle<UDPTrackerRequest> req =
+      findInflightRequest(remoteAddr, remotePort, transactionId, true);
+    if(!req) {
+      logInvalidTransaction(remoteAddr, remotePort, action, transactionId);
+      return -1;
+    }
+    req->state = UDPT_STA_COMPLETE;
+
+    int64_t connectionId = bittorrent::getLLIntParam(data, 8);
+    A2_LOG_INFO(fmt("UDPT received CONNECT reply from %s:%u transaction_id=%u,"
+                    "connection_id=%"PRId64, remoteAddr.c_str(), remotePort,
+                    transactionId, connectionId));
+    UDPTrackerConnection c(UDPT_CST_CONNECTED, connectionId, now);
+    connectionIdCache_[std::make_pair(remoteAddr, remotePort)] = c;
+    // Now we have connecion ID, push requests which are waiting for
+    // it.
+    std::vector<SharedHandle<UDPTrackerRequest> > reqs;
+    connectRequests_.erase(std::remove_if
+                           (connectRequests_.begin(), connectRequests_.end(),
+                            CollectAddrPortMatch(reqs, remoteAddr, remotePort)),
+                           connectRequests_.end());
+    pendingRequests_.insert(pendingRequests_.begin(),
+                            reqs.begin(), reqs.end());
+    break;
+  }
+  case UDPT_ACT_ANNOUNCE: {
+    if(length < 20) {
+      logTooShortLength(remoteAddr, remotePort, action, 20, length);
+      return - 1;
+    }
+    int32_t transactionId = bittorrent::getIntParam(data, 4);
+    SharedHandle<UDPTrackerRequest> req =
+      findInflightRequest(remoteAddr, remotePort, transactionId, true);
+    if(!req) {
+      logInvalidTransaction(remoteAddr, remotePort, action, transactionId);
+      return -1;
+    }
+    req->state = UDPT_STA_COMPLETE;
+
+    req->reply.reset(new UDPTrackerReply());
+    req->reply->action = action;
+    req->reply->transactionId = transactionId;
+    req->reply->interval = bittorrent::getIntParam(data, 8);
+    req->reply->leechers = bittorrent::getIntParam(data, 12);
+    req->reply->seeders = bittorrent::getIntParam(data, 16);
+
+    int numPeers = 0;
+    for(size_t i = 20; i < length; i += 6) {
+      std::pair<std::string, uint16_t> hostport =
+        bittorrent::unpackcompact(data+i, AF_INET);
+      if(!hostport.first.empty()) {
+        req->reply->peers.push_back(hostport);
+        ++numPeers;
+      }
+    }
+
+    A2_LOG_INFO(fmt("UDPT received ANNOUNCE reply from %s:%u transaction_id=%u,"
+                    "connection_id=%"PRId64", event=%s, infohash=%s, "
+                    "interval=%d, leechers=%d, "
+                    "seeders=%d, num_peers=%d", remoteAddr.c_str(), remotePort,
+                    transactionId, req->connectionId,
+                    getUDPTrackerEventStr(req->event),
+                    util::toHex(req->infohash).c_str(),
+                    req->reply->interval, req->reply->leechers,
+                    req->reply->seeders, numPeers));
+    break;
+  }
+  case UDPT_ACT_ERROR: {
+    if(length < 8) {
+      logTooShortLength(remoteAddr, remotePort, action, 8, length);
+      return -1;
+    }
+    int32_t transactionId = bittorrent::getIntParam(data, 4);
+    SharedHandle<UDPTrackerRequest> req =
+      findInflightRequest(remoteAddr, remotePort, transactionId, true);
+    if(!req) {
+      logInvalidTransaction(remoteAddr, remotePort, action, transactionId);
+      return -1;
+    }
+    std::string errorString(data+8, data+length);
+    errorString = util::encodeNonUtf8(errorString);
+
+    req->state = UDPT_STA_COMPLETE;
+    req->error = UDPT_ERR_TRACKER;
+
+    A2_LOG_INFO(fmt("UDPT received ERROR reply from %s:%u transaction_id=%u,"
+                    "connection_id=%"PRId64", action=%d, error_string=%s",
+                    remoteAddr.c_str(),
+                    remotePort, transactionId, req->connectionId, action,
+                    errorString.c_str()));
+    if(req->action == UDPT_ACT_CONNECT) {
+      failConnect(req->remoteAddr, req->remotePort, UDPT_ERR_TRACKER);
+    }
+    break;
+  }
+  case UDPT_ACT_SCRAPE:
+    A2_LOG_INFO("unexpected scrape action reply");
+    return -1;
+  default:
+    A2_LOG_INFO("unknown action reply");
+    return -1;
+  }
+  return 0;
+}
+
+ssize_t UDPTrackerClient::createRequest
+(unsigned char* data, size_t length, std::string& remoteAddr,
+ uint16_t& remotePort, const Timer& now)
+{
+  if(pendingRequests_.empty()) {
+    return -1;
+  }
+  while(!pendingRequests_.empty()) {
+    const SharedHandle<UDPTrackerRequest>& req = pendingRequests_.front();
+    if(req->action == UDPT_ACT_CONNECT) {
+      ssize_t rv;
+      rv = createUDPTrackerConnect(data, length, remoteAddr, remotePort, req);
+      return rv;
+    }
+    UDPTrackerConnection* c = getConnectionId(req->remoteAddr,
+                                              req->remotePort,
+                                              now);
+    if(!c) {
+      SharedHandle<UDPTrackerRequest> creq(new UDPTrackerRequest());
+      creq->action = UDPT_ACT_CONNECT;
+      creq->remoteAddr = req->remoteAddr;
+      creq->remotePort = req->remotePort;
+      creq->transactionId = generateTransactionId();
+      pendingRequests_.push_front(creq);
+      ssize_t rv;
+      rv = createUDPTrackerConnect(data, length, remoteAddr, remotePort, creq);
+      return rv;
+    }
+    if(c->state == UDPT_CST_CONNECTING) {
+      connectRequests_.push_back(req);
+      pendingRequests_.pop_front();
+      continue;
+    }
+    req->connectionId = c->connectionId;
+    req->transactionId = generateTransactionId();
+    ssize_t rv;
+    rv = createUDPTrackerAnnounce(data, length, remoteAddr, remotePort, req);
+    return rv;
+  }
+  return -1;
+}
+
+void UDPTrackerClient::requestSent(const Timer& now)
+{
+  if(pendingRequests_.empty()) {
+    A2_LOG_WARN("pendingRequests_ is empty");
+    return;
+  }
+  const SharedHandle<UDPTrackerRequest>& req = pendingRequests_.front();
+  switch(req->action) {
+  case UDPT_ACT_CONNECT:
+    A2_LOG_INFO(fmt("UDPT sent CONNECT to %s:%u transaction_id=%u",
+                    req->remoteAddr.c_str(), req->remotePort,
+                    req->transactionId));
+    break;
+  case UDPT_ACT_ANNOUNCE:
+    A2_LOG_INFO(fmt("UDPT sent ANNOUNCE to %s:%u transaction_id=%u, "
+                    "connection_id=%"PRId64", event=%s, infohash=%s",
+                    req->remoteAddr.c_str(), req->remotePort,
+                    req->transactionId, req->connectionId,
+                    getUDPTrackerEventStr(req->event),
+                    util::toHex(req->infohash).c_str()));
+    break;
+  default:
+    // unreachable
+    assert(0);
+  }
+  req->dispatched = now;
+  switch(req->action) {
+  case UDPT_ACT_CONNECT: {
+    connectionIdCache_[std::make_pair(req->remoteAddr, req->remotePort)]
+      = UDPTrackerConnection();
+    break;
+  }
+  }
+  inflightRequests_.push_back(req);
+  pendingRequests_.pop_front();
+}
+
+void UDPTrackerClient::requestFail(int error)
+{
+  if(pendingRequests_.empty()) {
+    A2_LOG_WARN("pendingRequests_ is empty");
+    return;
+  }
+  const SharedHandle<UDPTrackerRequest>& req = pendingRequests_.front();
+  switch(req->action) {
+  case UDPT_ACT_CONNECT:
+    A2_LOG_INFO(fmt("UDPT fail CONNECT to %s:%u transaction_id=%u",
+                    req->remoteAddr.c_str(), req->remotePort,
+                    req->transactionId));
+    failConnect(req->remoteAddr, req->remotePort, error);
+    break;
+  case UDPT_ACT_ANNOUNCE:
+    A2_LOG_INFO(fmt("UDPT fail ANNOUNCE to %s:%u transaction_id=%u, "
+                    "connection_id=%"PRId64", event=%s, infohash=%s",
+                    req->remoteAddr.c_str(), req->remotePort,
+                    req->transactionId, req->connectionId,
+                    getUDPTrackerEventStr(req->event),
+                    util::toHex(req->infohash).c_str()));
+    break;
+  default:
+    // unreachable
+    assert(0);
+  }
+  req->state = UDPT_STA_COMPLETE;
+  req->error = error;
+  pendingRequests_.pop_front();
+}
+
+void UDPTrackerClient::addRequest(const SharedHandle<UDPTrackerRequest>& req)
+{
+  req->state = UDPT_STA_PENDING;
+  req->error = UDPT_ERR_SUCCESS;
+  pendingRequests_.push_back(req);
+}
+
+namespace {
+struct TimeoutCheck {
+  bool operator()(const SharedHandle<UDPTrackerRequest>& req) const
+  {
+    int t = req->dispatched.difference(now);
+    if(req->failCount == 0) {
+      if(t >= 15) {
+        switch(req->action) {
+        case UDPT_ACT_CONNECT:
+          A2_LOG_INFO(fmt("UDPT resend CONNECT to %s:%u transaction_id=%u",
+                          req->remoteAddr.c_str(), req->remotePort,
+                          req->transactionId));
+          break;
+        case  UDPT_ACT_ANNOUNCE:
+          A2_LOG_INFO(fmt("UDPT resend ANNOUNCE to %s:%u transaction_id=%u, "
+                          "connection_id=%"PRId64", event=%s, infohash=%s",
+                          req->remoteAddr.c_str(), req->remotePort,
+                          req->transactionId, req->connectionId,
+                          getUDPTrackerEventStr(req->event),
+                          util::toHex(req->infohash).c_str()));
+          break;
+        default:
+          // unreachable
+          assert(0);
+        }
+        ++req->failCount;
+        dest.push_back(req);
+        return true;
+      } else {
+        return false;
+      }
+    } else {
+      if(t >= 60) {
+        switch(req->action) {
+        case UDPT_ACT_CONNECT:
+          A2_LOG_INFO(fmt("UDPT timeout CONNECT to %s:%u transaction_id=%u",
+                          req->remoteAddr.c_str(), req->remotePort,
+                          req->transactionId));
+          client->failConnect(req->remoteAddr, req->remotePort,
+                              UDPT_ERR_TIMEOUT);
+          break;
+        case UDPT_ACT_ANNOUNCE:
+          A2_LOG_INFO(fmt("UDPT timeout ANNOUNCE to %s:%u transaction_id=%u, "
+                          "connection_id=%"PRId64", event=%s, infohash=%s",
+                          req->remoteAddr.c_str(), req->remotePort,
+                          req->transactionId, req->connectionId,
+                          getUDPTrackerEventStr(req->event),
+                          util::toHex(req->infohash).c_str()));
+          break;
+        default:
+          // unreachable
+          assert(0);
+        }
+        ++req->failCount;
+        req->state = UDPT_STA_COMPLETE;
+        req->error = UDPT_ERR_TIMEOUT;
+        return true;
+      } else {
+        return false;
+      }
+    }
+  }
+  std::vector<SharedHandle<UDPTrackerRequest> >& dest;
+  UDPTrackerClient* client;
+  const Timer& now;
+  TimeoutCheck(std::vector<SharedHandle<UDPTrackerRequest> >& dest,
+               UDPTrackerClient* client,
+               const Timer& now)
+    : dest(dest), client(client), now(now)
+  {}
+};
+} // namespace
+
+void UDPTrackerClient::handleTimeout(const Timer& now)
+{
+  std::vector<SharedHandle<UDPTrackerRequest> > dest;
+  inflightRequests_.erase(std::remove_if(inflightRequests_.begin(),
+                                         inflightRequests_.end(),
+                                         TimeoutCheck(dest, this, now)),
+                          inflightRequests_.end());
+  pendingRequests_.insert(pendingRequests_.begin(), dest.begin(), dest.end());
+}
+
+SharedHandle<UDPTrackerRequest> UDPTrackerClient::findInflightRequest
+(const std::string& remoteAddr, uint16_t remotePort, int32_t transactionId,
+ bool remove)
+{
+  SharedHandle<UDPTrackerRequest> res;
+  for(std::deque<SharedHandle<UDPTrackerRequest> >::iterator i =
+        inflightRequests_.begin(), eoi = inflightRequests_.end(); i != eoi;
+      ++i) {
+    if((*i)->remoteAddr == remoteAddr && (*i)->remotePort == remotePort &&
+       (*i)->transactionId == transactionId) {
+      res = *i;
+      if(remove) {
+        inflightRequests_.erase(i);
+      }
+      break;
+    }
+  }
+  return res;
+}
+
+UDPTrackerConnection* UDPTrackerClient::getConnectionId
+(const std::string& remoteAddr, uint16_t remotePort, const Timer& now)
+{
+  std::map<std::pair<std::string, uint16_t>,
+           UDPTrackerConnection>::iterator i =
+    connectionIdCache_.find(std::make_pair(remoteAddr, remotePort));
+  if(i == connectionIdCache_.end()) {
+    return 0;
+  }
+  if((*i).second.state == UDPT_CST_CONNECTED &&
+     (*i).second.lastUpdated.difference(now) > 60) {
+    connectionIdCache_.erase(i);
+    return 0;
+  } else {
+    return &(*i).second;
+  }
+}
+
+namespace {
+struct FailConnectDelete {
+  bool operator()(const SharedHandle<UDPTrackerRequest>& req) const
+  {
+    if(req->action == UDPT_ACT_ANNOUNCE &&
+       req->remoteAddr == remoteAddr && req->remotePort == remotePort) {
+      A2_LOG_INFO(fmt("Force fail infohash=%s",
+                      util::toHex(req->infohash).c_str()));
+      req->state = UDPT_STA_COMPLETE;
+      req->error = error;
+      return true;
+    } else {
+      return false;
+    }
+  }
+  std::string remoteAddr;
+  uint16_t remotePort;
+  int error;
+  FailConnectDelete(const std::string& remoteAddr, uint16_t remotePort,
+                    int error)
+    : remoteAddr(remoteAddr), remotePort(remotePort), error(error)
+  {}
+};
+} // namespace
+
+void UDPTrackerClient::failConnect(const std::string& remoteAddr,
+                                   uint16_t remotePort, int error)
+{
+  connectionIdCache_.erase(std::make_pair(remoteAddr, remotePort));
+  // Fail all requests which are waiting for connecion ID of the host.
+  connectRequests_.erase(std::remove_if(connectRequests_.begin(),
+                                        connectRequests_.end(),
+                                        FailConnectDelete
+                                        (remoteAddr, remotePort, error)),
+                         connectRequests_.end());
+  pendingRequests_.erase(std::remove_if(pendingRequests_.begin(),
+                                        pendingRequests_.end(),
+                                        FailConnectDelete
+                                        (remoteAddr, remotePort, error)),
+                         pendingRequests_.end());
+}
+
+void UDPTrackerClient::failAll()
+{
+  int error = UDPT_ERR_SHUTDOWN;
+  failRequest(inflightRequests_.begin(), inflightRequests_.end(), error);
+  failRequest(pendingRequests_.begin(), pendingRequests_.end(), error);
+  failRequest(connectRequests_.begin(), connectRequests_.end(), error);
+}
+
+void UDPTrackerClient::increaseWatchers()
+{
+  ++numWatchers_;
+}
+
+void UDPTrackerClient::decreaseWatchers()
+{
+  --numWatchers_;
+}
+
+ssize_t createUDPTrackerConnect
+(unsigned char* data, size_t length,
+ std::string& remoteAddr, uint16_t& remotePort,
+ const SharedHandle<UDPTrackerRequest>& req)
+{
+  assert(length >= 16);
+  remoteAddr = req->remoteAddr;
+  remotePort = req->remotePort;
+  bittorrent::setLLIntParam(data, UDPT_INITIAL_CONNECTION_ID);
+  bittorrent::setIntParam(data+8, req->action);
+  bittorrent::setIntParam(data+12, req->transactionId);
+  return 16;
+}
+
+ssize_t createUDPTrackerAnnounce
+(unsigned char* data, size_t length,
+ std::string& remoteAddr, uint16_t& remotePort,
+ const SharedHandle<UDPTrackerRequest>& req)
+{
+  assert(length >= 100);
+  remoteAddr = req->remoteAddr;
+  remotePort = req->remotePort;
+  bittorrent::setLLIntParam(data, req->connectionId);
+  bittorrent::setIntParam(data+8, req->action);
+  bittorrent::setIntParam(data+12, req->transactionId);
+  memcpy(data+16, req->infohash.c_str(), req->infohash.size());
+  memcpy(data+36, req->peerId.c_str(), req->peerId.size());
+  bittorrent::setLLIntParam(data+56, req->downloaded);
+  bittorrent::setLLIntParam(data+64, req->left);
+  bittorrent::setLLIntParam(data+72, req->uploaded);
+  bittorrent::setIntParam(data+80, req->event);
+  // ip is already network-byte order
+  memcpy(data+84, &req->ip, sizeof(req->ip));
+  bittorrent::setIntParam(data+88, req->key);
+  bittorrent::setIntParam(data+92, req->numWant);
+  bittorrent::setShortIntParam(data+96, req->port);
+  // extensions is always 0
+  bittorrent::setShortIntParam(data+98, 0);
+  return 100;
+}
+
+const char* getUDPTrackerActionStr(int action)
+{
+  switch(action) {
+  case UDPT_ACT_CONNECT:
+    return "CONNECT";
+  case UDPT_ACT_ANNOUNCE:
+    return "ANNOUNCE";
+  case UDPT_ACT_ERROR:
+    return "ERROR";
+  default:
+    return "(unknown)";
+  }
+}
+
+const char* getUDPTrackerEventStr(int event)
+{
+  switch(event) {
+  case UDPT_EVT_NONE:
+    return "NONE";
+  case UDPT_EVT_COMPLETED:
+    return "COMPLETED";
+  case UDPT_EVT_STARTED:
+    return "STARTED";
+  case UDPT_EVT_STOPPED:
+    return "STOPPED";
+  default:
+    return "(unknown)";
+  }
+}
+
+} // namespace aria2

+ 171 - 0
src/UDPTrackerClient.h

@@ -0,0 +1,171 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 Tatsuhiro Tsujikawa
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#ifndef D_UDP_TRACKER_CLIENT_H
+#define D_UDP_TRACKER_CLIENT_H
+
+#include "common.h"
+
+#include <string>
+#include <deque>
+#include <map>
+
+#include "SharedHandle.h"
+#include "TimerA2.h"
+
+namespace aria2 {
+
+#define UDPT_INITIAL_CONNECTION_ID 0x41727101980LL
+
+class UDPTrackerRequest;
+
+enum UDPTrackerConnectionState {
+  UDPT_CST_CONNECTING,
+  UDPT_CST_CONNECTED
+};
+
+struct UDPTrackerConnection {
+  int state;
+  int64_t connectionId;
+  Timer lastUpdated;
+  UDPTrackerConnection()
+    : state(UDPT_CST_CONNECTING),
+      connectionId(UDPT_INITIAL_CONNECTION_ID),
+      lastUpdated(0)
+  {}
+  UDPTrackerConnection(int state, int64_t connectionId,
+                       const Timer& lastUpdated)
+    : state(state),
+      connectionId(connectionId),
+      lastUpdated(lastUpdated)
+  {}
+};
+
+class UDPTrackerClient {
+public:
+  UDPTrackerClient();
+  ~UDPTrackerClient();
+
+  int receiveReply
+  (const unsigned char* data, size_t length, const std::string& remoteAddr,
+   uint16_t remotePort, const Timer& now);
+
+  // Creates data frame for the next pending request. This function
+  // always processes first entry of pendingRequests_.  If the data is
+  // sent successfully, call requestSent(). Otherwise call
+  // requestFail().
+  ssize_t createRequest
+  (unsigned char* data, size_t length, std::string& remoteAddr,
+   uint16_t& remotePort, const Timer& now);
+
+  // Tells this object that first entry of pendingRequests_ is
+  // successfully sent.
+  void requestSent(const Timer& now);
+  // Tells this object that first entry of pendingRequests_ is not
+  // successfully sent. The |error| should indicate error situation.
+  void requestFail(int error);
+
+  void addRequest(const SharedHandle<UDPTrackerRequest>& req);
+
+  // Handles timeout for inflight requests.
+  void handleTimeout(const Timer& now);
+
+  const std::deque<SharedHandle<UDPTrackerRequest> >&
+  getPendingRequests() const
+  {
+    return pendingRequests_;
+  }
+  const std::deque<SharedHandle<UDPTrackerRequest> >&
+  getConnectRequests() const
+  {
+    return connectRequests_;
+  }
+  const std::deque<SharedHandle<UDPTrackerRequest> >&
+  getInflightRequests() const
+  {
+    return inflightRequests_;
+  }
+
+  bool noRequest() const
+  {
+    return pendingRequests_.empty() && connectRequests_.empty() &&
+      getInflightRequests().empty();
+  }
+
+  // Makes all contained requests fail.
+  void failAll();
+
+  int getNumWatchers() const
+  {
+    return numWatchers_;
+  }
+
+  void increaseWatchers();
+  void decreaseWatchers();
+
+  // Actually private function, but made public, to be used by unnamed
+  // function.
+  void failConnect(const std::string& remoteAddr, uint16_t remotePort,
+                   int error);
+private:
+  SharedHandle<UDPTrackerRequest> findInflightRequest
+  (const std::string& remoteAddr, uint16_t remotePort, int32_t transactionId,
+   bool remove);
+
+  UDPTrackerConnection* getConnectionId
+  (const std::string& remoteAddr, uint16_t remotePort, const Timer& now);
+
+  std::map<std::pair<std::string, uint16_t>,
+           UDPTrackerConnection> connectionIdCache_;
+  std::deque<SharedHandle<UDPTrackerRequest> > inflightRequests_;
+  std::deque<SharedHandle<UDPTrackerRequest> > pendingRequests_;
+  std::deque<SharedHandle<UDPTrackerRequest> > connectRequests_;
+  int numWatchers_;
+};
+
+ssize_t createUDPTrackerConnect
+(unsigned char* data, size_t length, std::string& remoteAddr,
+ uint16_t& remotePort, const SharedHandle<UDPTrackerRequest>& req);
+
+ssize_t createUDPTrackerAnnounce
+(unsigned char* data, size_t length, std::string& remoteAddr,
+ uint16_t& remotePort, const SharedHandle<UDPTrackerRequest>& req);
+
+const char* getUDPTrackerActionStr(int action);
+
+const char* getUDPTrackerEventStr(int event);
+
+} // namespace aria2
+
+#endif // D_UDP_TRACKER_CLIENT_H

+ 51 - 0
src/UDPTrackerRequest.cc

@@ -0,0 +1,51 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 Tatsuhiro Tsujikawa
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "UDPTrackerRequest.h"
+
+namespace aria2 {
+
+UDPTrackerReply::UDPTrackerReply()
+  : action(0), transactionId(0), interval(0), leechers(0), seeders(0)
+{}
+
+UDPTrackerRequest::UDPTrackerRequest()
+  : remotePort(0), action(UDPT_ACT_CONNECT), transactionId(0), downloaded(0),
+    left(0), uploaded(0), event(UDPT_EVT_NONE), ip(0), key(0), numWant(0),
+    port(0), extensions(0), state(UDPT_STA_PENDING), error(UDPT_ERR_SUCCESS),
+    dispatched(0),
+    failCount(0)
+{}
+
+} // namespace aria2

+ 112 - 0
src/UDPTrackerRequest.h

@@ -0,0 +1,112 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 Tatsuhiro Tsujikawa
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#ifndef D_UDP_TRACKER_REQUEST_H
+#define D_UDP_TRACKER_REQUEST_H
+
+#include "common.h"
+
+#include <string>
+#include <vector>
+
+#include "SharedHandle.h"
+#include "TimerA2.h"
+
+namespace aria2 {
+
+enum UDPTrackerAction {
+  UDPT_ACT_CONNECT = 0,
+  UDPT_ACT_ANNOUNCE = 1,
+  UDPT_ACT_SCRAPE = 2,
+  UDPT_ACT_ERROR = 3
+};
+
+enum UDPTrackerError {
+  UDPT_ERR_SUCCESS,
+  UDPT_ERR_TRACKER,
+  UDPT_ERR_TIMEOUT,
+  UDPT_ERR_NETWORK,
+  UDPT_ERR_SHUTDOWN
+};
+
+enum UDPTrackerState {
+  UDPT_STA_PENDING,
+  UDPT_STA_COMPLETE
+};
+
+enum UDPTrackerEvent {
+  UDPT_EVT_NONE = 0,
+  UDPT_EVT_COMPLETED = 1,
+  UDPT_EVT_STARTED = 2,
+  UDPT_EVT_STOPPED = 3
+};
+
+struct UDPTrackerReply {
+  int32_t action;
+  int32_t transactionId;
+  int32_t interval;
+  int32_t leechers;
+  int32_t seeders;
+  std::vector<std::pair<std::string, uint16_t> > peers;
+  UDPTrackerReply();
+};
+
+struct UDPTrackerRequest {
+  std::string remoteAddr;
+  uint16_t remotePort;
+  int64_t connectionId;
+  int32_t action;
+  int32_t transactionId;
+  std::string infohash;
+  std::string peerId;
+  int64_t downloaded;
+  int64_t left;
+  int64_t uploaded;
+  int32_t event;
+  uint32_t ip;
+  uint32_t key;
+  int32_t numWant;
+  uint16_t port;
+  uint16_t extensions;
+  int state;
+  int error;
+  Timer dispatched;
+  int failCount;
+  SharedHandle<UDPTrackerReply> reply;
+  UDPTrackerRequest();
+};
+
+} // namespace aria2
+
+#endif // D_UDP_TRACKER_REQUEST_H

+ 13 - 0
src/bittorrent_helper.cc

@@ -731,6 +731,13 @@ uint8_t getId(const unsigned char* msg)
   return msg[0];
 }
 
+uint64_t getLLIntParam(const unsigned char* msg, size_t pos)
+{
+  uint64_t nParam;
+  memcpy(&nParam, msg+pos, sizeof(nParam));
+  return ntoh64(nParam);
+}
+
 uint32_t getIntParam(const unsigned char* msg, size_t pos)
 {
   uint32_t nParam;
@@ -798,6 +805,12 @@ void checkBitfield
   }
 }
 
+void setLLIntParam(unsigned char* dest, uint64_t param)
+{
+  uint64_t nParam = hton64(param);
+  memcpy(dest, &nParam, sizeof(nParam));
+}
+
 void setIntParam(unsigned char* dest, uint32_t param)
 {
   uint32_t nParam = htonl(param);

+ 9 - 0
src/bittorrent_helper.h

@@ -160,6 +160,11 @@ getInfoHash(const SharedHandle<DownloadContext>& downloadContext);
 std::string
 getInfoHashString(const SharedHandle<DownloadContext>& downloadContext);
 
+// Returns 8bytes unsigned integer located at offset pos.  The integer
+// in msg is network byte order. This function converts it into host
+// byte order and returns it.
+uint64_t getLLIntParam(const unsigned char* msg, size_t pos);
+
 // Returns 4bytes unsigned integer located at offset pos.  The integer
 // in msg is network byte order. This function converts it into host
 // byte order and returns it.
@@ -170,6 +175,10 @@ uint32_t getIntParam(const unsigned char* msg, size_t pos);
 // byte order and returns it.
 uint16_t getShortIntParam(const unsigned char* msg, size_t pos);
 
+// Put param at location pointed by dest. param is converted into
+// network byte order.
+void setLLIntParam(unsigned char* dest, uint64_t param);
+
 // Put param at location pointed by dest. param is converted into
 // network byte order.
 void setIntParam(unsigned char* dest, uint32_t param);

+ 1 - 0
test/BtRegistryTest.cc

@@ -11,6 +11,7 @@
 #include "BtRuntime.h"
 #include "FileEntry.h"
 #include "bittorrent_helper.h"
+#include "UDPTrackerRequest.h"
 
 namespace aria2 {
 

+ 66 - 0
test/DefaultBtAnnounceTest.cc

@@ -18,6 +18,8 @@
 #include "DownloadContext.h"
 #include "bittorrent_helper.h"
 #include "array_fun.h"
+#include "UDPTrackerRequest.h"
+#include "SocketCore.h"
 
 namespace aria2 {
 
@@ -34,6 +36,7 @@ class DefaultBtAnnounceTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testProcessAnnounceResponse_malformed);
   CPPUNIT_TEST(testProcessAnnounceResponse_failureReason);
   CPPUNIT_TEST(testProcessAnnounceResponse);
+  CPPUNIT_TEST(testProcessUDPTrackerResponse);
   CPPUNIT_TEST_SUITE_END();
 private:
   SharedHandle<DownloadContext> dctx_;
@@ -87,6 +90,7 @@ public:
   void testProcessAnnounceResponse_malformed();
   void testProcessAnnounceResponse_failureReason();
   void testProcessAnnounceResponse();
+  void testProcessUDPTrackerResponse();
 };
 
 
@@ -197,24 +201,49 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl()
   btAnnounce.setBtRuntime(btRuntime_);
   btAnnounce.setRandomizer(SharedHandle<Randomizer>(new FixedNumberRandomizer()));
   btAnnounce.setTcpPort(6989);
+  SharedHandle<UDPTrackerRequest> req;
 
   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&downloaded=1310720&left=1572864&compact=1&key=fastdltl&numwant=50&no_peer_id=1&port=6989&event=started&supportcrypto=1"), btAnnounce.getAnnounceUrl());
+  req = btAnnounce.createUDPTrackerRequest("localhost", 80, 6989);
+  CPPUNIT_ASSERT_EQUAL(std::string("localhost"), req->remoteAddr);
+  CPPUNIT_ASSERT_EQUAL((uint16_t)80, req->remotePort);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE, req->action);
+  CPPUNIT_ASSERT_EQUAL(bittorrent::getInfoHashString(dctx_),
+                       util::toHex(req->infohash));
+  CPPUNIT_ASSERT_EQUAL(std::string("-aria2-ultrafastdltl"), req->peerId);
+  CPPUNIT_ASSERT_EQUAL((int64_t)1310720, req->downloaded);
+  CPPUNIT_ASSERT_EQUAL((int64_t)1572864, req->left);
+  CPPUNIT_ASSERT_EQUAL((int64_t)1572864, req->uploaded);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_EVT_STARTED, req->event);
+  CPPUNIT_ASSERT_EQUAL((uint32_t)0, req->ip);
+  CPPUNIT_ASSERT_EQUAL((int32_t)50, req->numWant);
+  CPPUNIT_ASSERT_EQUAL((uint16_t)6989, req->port);
+  CPPUNIT_ASSERT_EQUAL((uint16_t)0, req->extensions);
 
   btAnnounce.announceSuccess();
 
   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&downloaded=1310720&left=1572864&compact=1&key=fastdltl&numwant=50&no_peer_id=1&port=6989&supportcrypto=1"), btAnnounce.getAnnounceUrl());
+  req = btAnnounce.createUDPTrackerRequest("localhost", 80, 6989);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE, req->action);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_EVT_NONE, req->event);
 
   btAnnounce.announceSuccess();
 
   pieceStorage_->setAllDownloadFinished(true);
 
   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&downloaded=1310720&left=1572864&compact=1&key=fastdltl&numwant=50&no_peer_id=1&port=6989&event=completed&supportcrypto=1"), btAnnounce.getAnnounceUrl());
+  req = btAnnounce.createUDPTrackerRequest("localhost", 80, 6989);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE, req->action);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_EVT_COMPLETED, req->event);
 
   btAnnounce.announceSuccess();
 
   btRuntime_->setHalt(true);
 
   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&downloaded=1310720&left=1572864&compact=1&key=fastdltl&numwant=0&no_peer_id=1&port=6989&event=stopped&supportcrypto=1"), btAnnounce.getAnnounceUrl());
+  req = btAnnounce.createUDPTrackerRequest("localhost", 80, 6989);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE, req->action);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_EVT_STOPPED, req->event);
 }
 
 void DefaultBtAnnounceTest::testGetAnnounceUrl_withQuery()
@@ -262,6 +291,13 @@ void DefaultBtAnnounceTest::testGetAnnounceUrl_externalIP()
                  "key=fastdltl&numwant=50&no_peer_id=1&port=6989&event=started&"
                  "supportcrypto=1&ip=192.168.1.1"),
      btAnnounce.getAnnounceUrl());
+
+  SharedHandle<UDPTrackerRequest> req;
+  req = btAnnounce.createUDPTrackerRequest("localhost", 80, 6989);
+  char host[NI_MAXHOST];
+  int rv = inetNtop(AF_INET, &req->ip, host, sizeof(host));
+  CPPUNIT_ASSERT_EQUAL(0, rv);
+  CPPUNIT_ASSERT_EQUAL(std::string("192.168.1.1"), std::string(host));
 }
 
 void DefaultBtAnnounceTest::testIsAllAnnounceFailed()
@@ -411,4 +447,34 @@ void DefaultBtAnnounceTest::testProcessAnnounceResponse()
                        peer->getIPAddress());
 }
 
+void DefaultBtAnnounceTest::testProcessUDPTrackerResponse()
+{
+  SharedHandle<UDPTrackerRequest> req(new UDPTrackerRequest());
+  req->action = UDPT_ACT_ANNOUNCE;
+  SharedHandle<UDPTrackerReply> reply(new UDPTrackerReply());
+  reply->interval = 1800;
+  reply->leechers = 200;
+  reply->seeders = 100;
+  for(int i = 0; i < 2; ++i) {
+    reply->peers.push_back(std::make_pair("192.168.0."+util::uitos(i+1),
+                                          6890+i));
+  }
+  req->reply = reply;
+  DefaultBtAnnounce an(dctx_, option_);
+  an.setPeerStorage(peerStorage_);
+  an.setBtRuntime(btRuntime_);
+  an.processUDPTrackerResponse(req);
+  CPPUNIT_ASSERT_EQUAL((time_t)1800, an.getInterval());
+  CPPUNIT_ASSERT_EQUAL((time_t)1800, an.getMinInterval());
+  CPPUNIT_ASSERT_EQUAL(100, an.getComplete());
+  CPPUNIT_ASSERT_EQUAL(200, an.getIncomplete());
+  CPPUNIT_ASSERT_EQUAL((size_t)2, peerStorage_->getUnusedPeers().size());
+  for(int i = 0; i < 2; ++i) {
+    SharedHandle<Peer> peer;
+    peer = peerStorage_->getUnusedPeers()[i];
+    CPPUNIT_ASSERT_EQUAL("192.168.0."+util::uitos(i+1), peer->getIPAddress());
+    CPPUNIT_ASSERT_EQUAL((uint16_t)(6890+i), peer->getPort());
+  }
+}
+
 } // namespace aria2

+ 2 - 1
test/Makefile.am

@@ -214,7 +214,8 @@ aria2c_SOURCES += BtAllowedFastMessageTest.cc\
 	Bencode2Test.cc\
 	PeerConnectionTest.cc\
 	ValueBaseBencodeParserTest.cc\
-	ExtensionMessageRegistryTest.cc
+	ExtensionMessageRegistryTest.cc\
+	UDPTrackerClientTest.cc
 endif # ENABLE_BITTORRENT
 
 if ENABLE_METALINK

+ 9 - 0
test/MockBtAnnounce.h

@@ -26,6 +26,12 @@ public:
     return announceUrl;
   }
 
+  virtual SharedHandle<UDPTrackerRequest>
+  createUDPTrackerRequest(const std::string& remoteAddr, uint16_t remotePort,
+                          uint16_t localPort) {
+    return SharedHandle<UDPTrackerRequest>();
+  }
+
   void setAnnounceUrl(const std::string& url) {
     this->announceUrl = url;
   }
@@ -45,6 +51,9 @@ public:
   virtual void processAnnounceResponse(const unsigned char* trackerResponse,
                                        size_t trackerResponseLength) {}
 
+  virtual void processUDPTrackerResponse
+  (const SharedHandle<UDPTrackerRequest>& req) {}
+
   virtual bool noMoreAnnounce() {
     return false;
   }

+ 453 - 0
test/UDPTrackerClientTest.cc

@@ -0,0 +1,453 @@
+#include "UDPTrackerClient.h"
+
+#include <cstring>
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "TestUtil.h"
+#include "UDPTrackerRequest.h"
+#include "bittorrent_helper.h"
+#include "wallclock.h"
+
+namespace aria2 {
+
+class UDPTrackerClientTest:public CppUnit::TestFixture {
+
+  CPPUNIT_TEST_SUITE(UDPTrackerClientTest);
+  CPPUNIT_TEST(testCreateUDPTrackerConnect);
+  CPPUNIT_TEST(testCreateUDPTrackerAnnounce);
+  CPPUNIT_TEST(testConnectFollowedByAnnounce);
+  CPPUNIT_TEST(testRequestFailure);
+  CPPUNIT_TEST(testTimeout);
+  CPPUNIT_TEST_SUITE_END();
+public:
+  void setUp()
+  {
+  }
+
+  void testCreateUDPTrackerConnect();
+  void testCreateUDPTrackerAnnounce();
+  void testConnectFollowedByAnnounce();
+  void testRequestFailure();
+  void testTimeout();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION( UDPTrackerClientTest );
+
+namespace {
+SharedHandle<UDPTrackerRequest> createAnnounce(const std::string& remoteAddr,
+                                               uint16_t remotePort,
+                                               int32_t transactionId)
+{
+  SharedHandle<UDPTrackerRequest> req(new UDPTrackerRequest());
+  req->connectionId = INT64_MAX;
+  req->action = UDPT_ACT_ANNOUNCE;
+  req->remoteAddr = remoteAddr;
+  req->remotePort = remotePort;
+  req->transactionId = transactionId;
+  req->infohash = "bittorrent-infohash-";
+  req->peerId =   "bittorrent-peer-id--";
+  req->downloaded = INT64_MAX - 1;
+  req->left = INT64_MAX - 2;
+  req->uploaded = INT64_MAX - 3;
+  req->event = UDPT_EVT_STARTED;
+  req->ip = 0;
+  req->key = 1000000007;
+  req->numWant = 50;
+  req->port = 6889;
+  req->extensions = 0;
+  return req;
+}
+} // namespace
+
+namespace {
+ssize_t createErrorReply(unsigned char* data, size_t len,
+                         int32_t transactionId, const std::string& errorString)
+{
+  bittorrent::setIntParam(data, UDPT_ACT_ERROR);
+  bittorrent::setIntParam(data+4, transactionId);
+  memcpy(data+8, errorString.c_str(), errorString.size());
+  return 8+errorString.size();
+}
+} // namespace
+
+namespace {
+ssize_t createConnectReply(unsigned char* data, size_t len,
+                           uint64_t connectionId, int32_t transactionId)
+{
+  bittorrent::setIntParam(data, UDPT_ACT_CONNECT);
+  bittorrent::setIntParam(data+4, transactionId);
+  bittorrent::setLLIntParam(data+8, connectionId);
+  return 16;
+}
+} // namespace
+
+namespace {
+ssize_t createAnnounceReply(unsigned char*data, size_t len,
+                            int32_t transactionId, int numPeers = 0)
+{
+  bittorrent::setIntParam(data, UDPT_ACT_ANNOUNCE);
+  bittorrent::setIntParam(data+4, transactionId);
+  bittorrent::setIntParam(data+8, 1800);
+  bittorrent::setIntParam(data+12, 100);
+  bittorrent::setIntParam(data+16, 256);
+  for(int i = 0; i < numPeers; ++i) {
+    bittorrent::packcompact(data+20+6*i, "192.168.0."+util::uitos(i+1),
+                            6990+i);
+  }
+  return 20 + 6 * numPeers;
+}
+} // namespace
+
+void UDPTrackerClientTest::testCreateUDPTrackerConnect()
+{
+  unsigned char data[16];
+  std::string remoteAddr;
+  uint16_t remotePort = 0;
+  SharedHandle<UDPTrackerRequest> req(new UDPTrackerRequest());
+  req->action = UDPT_ACT_CONNECT;
+  req->remoteAddr = "192.168.0.1";
+  req->remotePort = 6991;
+  req->transactionId = 1000000009;
+  ssize_t rv = createUDPTrackerConnect(data, sizeof(data), remoteAddr,
+                                       remotePort, req);
+  CPPUNIT_ASSERT_EQUAL((ssize_t)16, rv);
+  CPPUNIT_ASSERT_EQUAL(req->remoteAddr, remoteAddr);
+  CPPUNIT_ASSERT_EQUAL(req->remotePort, remotePort);
+  CPPUNIT_ASSERT_EQUAL((int64_t)UDPT_INITIAL_CONNECTION_ID,
+                       (int64_t)bittorrent::getLLIntParam(data, 0));
+  CPPUNIT_ASSERT_EQUAL((int)req->action, (int)bittorrent::getIntParam(data, 8));
+  CPPUNIT_ASSERT_EQUAL(req->transactionId,
+                       (int32_t)bittorrent::getIntParam(data, 12));
+}
+
+void UDPTrackerClientTest::testCreateUDPTrackerAnnounce()
+{
+  unsigned char data[100];
+  std::string remoteAddr;
+  uint16_t remotePort = 0;
+  SharedHandle<UDPTrackerRequest> req(createAnnounce("192.168.0.1", 6991,
+                                                     1000000009));
+  ssize_t rv = createUDPTrackerAnnounce(data, sizeof(data), remoteAddr,
+                                        remotePort, req);
+  CPPUNIT_ASSERT_EQUAL((ssize_t)100, rv);
+  CPPUNIT_ASSERT_EQUAL(req->connectionId,
+                       (int64_t)bittorrent::getLLIntParam(data, 0));
+  CPPUNIT_ASSERT_EQUAL((int)req->action, (int)bittorrent::getIntParam(data, 8));
+  CPPUNIT_ASSERT_EQUAL(req->transactionId,
+                       (int32_t)bittorrent::getIntParam(data, 12));
+  CPPUNIT_ASSERT_EQUAL(req->infohash, std::string(&data[16], &data[36]));
+  CPPUNIT_ASSERT_EQUAL(req->peerId, std::string(&data[36], &data[56]));
+  CPPUNIT_ASSERT_EQUAL(req->downloaded,
+                       (int64_t)bittorrent::getLLIntParam(data, 56));
+  CPPUNIT_ASSERT_EQUAL(req->left,
+                       (int64_t)bittorrent::getLLIntParam(data, 64));
+  CPPUNIT_ASSERT_EQUAL(req->uploaded,
+                       (int64_t)bittorrent::getLLIntParam(data, 72));
+  CPPUNIT_ASSERT_EQUAL(req->event, (int32_t)bittorrent::getIntParam(data, 80));
+  CPPUNIT_ASSERT_EQUAL(req->ip, bittorrent::getIntParam(data, 84));
+  CPPUNIT_ASSERT_EQUAL(req->key, bittorrent::getIntParam(data, 88));
+  CPPUNIT_ASSERT_EQUAL(req->numWant,
+                       (int32_t)bittorrent::getIntParam(data, 92));
+  CPPUNIT_ASSERT_EQUAL(req->port, bittorrent::getShortIntParam(data, 96));
+  CPPUNIT_ASSERT_EQUAL(req->extensions, bittorrent::getShortIntParam(data, 98));
+}
+
+void UDPTrackerClientTest::testConnectFollowedByAnnounce()
+{
+  ssize_t rv;
+  UDPTrackerClient tr;
+  unsigned char data[100];
+  std::string remoteAddr;
+  uint16_t remotePort;
+  Timer now;
+
+  SharedHandle<UDPTrackerRequest> req1(createAnnounce("192.168.0.1", 6991, 0));
+  SharedHandle<UDPTrackerRequest> req2(createAnnounce("192.168.0.1", 6991, 0));
+  req2->infohash = "bittorrent-infohash2";
+
+  tr.addRequest(req1);
+  tr.addRequest(req2);
+  CPPUNIT_ASSERT_EQUAL((size_t)2, tr.getPendingRequests().size());
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  // CONNECT request was inserted
+  CPPUNIT_ASSERT_EQUAL((size_t)3, tr.getPendingRequests().size());
+  CPPUNIT_ASSERT_EQUAL((ssize_t)16, rv);
+  CPPUNIT_ASSERT_EQUAL(req1->remoteAddr, remoteAddr);
+  CPPUNIT_ASSERT_EQUAL(req1->remotePort, remotePort);
+  CPPUNIT_ASSERT_EQUAL((int64_t)UDPT_INITIAL_CONNECTION_ID,
+                       (int64_t)bittorrent::getLLIntParam(data, 0));
+  int32_t transactionId = bittorrent::getIntParam(data, 12);
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  // Duplicate CONNECT request was not inserted
+  CPPUNIT_ASSERT_EQUAL((size_t)3, tr.getPendingRequests().size());
+  CPPUNIT_ASSERT_EQUAL((ssize_t)16, rv);
+
+  tr.requestSent(now);
+  // CONNECT request was moved to inflight
+  CPPUNIT_ASSERT_EQUAL((size_t)2, tr.getPendingRequests().size());
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  // Now all pending requests were moved to connect
+  CPPUNIT_ASSERT_EQUAL((ssize_t)-1, rv);
+  CPPUNIT_ASSERT(tr.getPendingRequests().empty());
+
+  int64_t connectionId = 12345;
+  rv = createConnectReply(data, sizeof(data), connectionId, transactionId);
+  rv = tr.receiveReply(data, rv, req1->remoteAddr, req1->remotePort, now);
+  CPPUNIT_ASSERT_EQUAL(0, (int)rv);
+  // Now 2 requests get back to pending
+  CPPUNIT_ASSERT_EQUAL((size_t)2, tr.getPendingRequests().size());
+
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  // Creates announce for req1
+  CPPUNIT_ASSERT_EQUAL((ssize_t)100, rv);
+  CPPUNIT_ASSERT_EQUAL((size_t)2, tr.getPendingRequests().size());
+  CPPUNIT_ASSERT_EQUAL(connectionId,
+                       (int64_t)bittorrent::getLLIntParam(data, 0));
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE,
+                       (int)bittorrent::getIntParam(data, 8));
+  CPPUNIT_ASSERT_EQUAL(req1->infohash,
+                       std::string(&data[16], &data[36]));
+
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  // Don't duplicate same request data
+  CPPUNIT_ASSERT_EQUAL((ssize_t)100, rv);
+  CPPUNIT_ASSERT_EQUAL((size_t)2, tr.getPendingRequests().size());
+  int32_t transactionId1 = bittorrent::getIntParam(data, 12);
+
+  tr.requestSent(now);
+  CPPUNIT_ASSERT_EQUAL((size_t)1, tr.getPendingRequests().size());
+
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  int32_t transactionId2 = bittorrent::getIntParam(data, 12);
+  // Creates announce for req2
+  CPPUNIT_ASSERT_EQUAL((ssize_t)100, rv);
+  CPPUNIT_ASSERT_EQUAL((size_t)1, tr.getPendingRequests().size());
+  CPPUNIT_ASSERT_EQUAL(connectionId,
+                       (int64_t)bittorrent::getLLIntParam(data, 0));
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE,
+                       (int)bittorrent::getIntParam(data, 8));
+  CPPUNIT_ASSERT_EQUAL(req2->infohash,
+                       std::string(&data[16], &data[36]));
+
+  tr.requestSent(now);
+  // Now all requests are inflight
+  CPPUNIT_ASSERT_EQUAL((size_t)0, tr.getPendingRequests().size());
+
+  // Reply for req2
+  rv = createAnnounceReply(data, sizeof(data), transactionId2);
+  rv = tr.receiveReply(data, rv, req2->remoteAddr, req2->remotePort, now);
+  CPPUNIT_ASSERT_EQUAL(0, (int)rv);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req2->state);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_SUCCESS, req2->error);
+
+  // Reply for req1
+  rv = createAnnounceReply(data, sizeof(data), transactionId1, 2);
+  rv = tr.receiveReply(data, rv, req1->remoteAddr, req1->remotePort, now);
+  CPPUNIT_ASSERT_EQUAL(0, (int)rv);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req1->state);
+  CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_SUCCESS, req1->error);
+  CPPUNIT_ASSERT_EQUAL((size_t)2, req1->reply->peers.size());
+  for(int i = 0; i < 2; ++i) {
+    CPPUNIT_ASSERT_EQUAL("192.168.0."+util::uitos(i+1),
+                         req1->reply->peers[i].first);
+    CPPUNIT_ASSERT_EQUAL((uint16_t)(6990+i), req1->reply->peers[i].second);
+  }
+
+  // Since we have connection ID, next announce request can be sent
+  // immediately
+  SharedHandle<UDPTrackerRequest> req3(createAnnounce("192.168.0.1", 6991, 0));
+  req3->infohash = "bittorrent-infohash3";
+  tr.addRequest(req3);
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+  CPPUNIT_ASSERT_EQUAL((ssize_t)100, rv);
+  CPPUNIT_ASSERT_EQUAL(req3->infohash,
+                       std::string(&data[16], &data[36]));
+
+  tr.requestSent(now);
+
+  SharedHandle<UDPTrackerRequest> req4(createAnnounce("192.168.0.1", 6991, 0));
+  req4->infohash = "bittorrent-infohash4";
+  tr.addRequest(req4);
+  Timer future = now;
+  future.advance(3600);
+  rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort,
+                        future);
+  // connection ID is stale because of the timeout
+  CPPUNIT_ASSERT_EQUAL((ssize_t)16, rv);
+  CPPUNIT_ASSERT_EQUAL((int64_t)UDPT_INITIAL_CONNECTION_ID,
+                       (int64_t)bittorrent::getLLIntParam(data, 0));
+}
+
+void UDPTrackerClientTest::testRequestFailure()
+{
+  ssize_t rv;
+  UDPTrackerClient tr;
+  unsigned char data[100];
+  std::string remoteAddr;
+  uint16_t remotePort;
+  Timer now;
+  {
+    SharedHandle<UDPTrackerRequest> req1
+      (createAnnounce("192.168.0.1", 6991, 0));
+    SharedHandle<UDPTrackerRequest> req2
+      (createAnnounce("192.168.0.1", 6991, 0));
+
+    tr.addRequest(req1);
+    tr.addRequest(req2);
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_CONNECT,
+                         (int)bittorrent::getIntParam(data, 8));
+    tr.requestFail(UDPT_ERR_NETWORK);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req1->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_NETWORK, req1->error);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req2->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_NETWORK, req2->error);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT(tr.getPendingRequests().empty());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+  }
+  {
+    SharedHandle<UDPTrackerRequest> req1
+      (createAnnounce("192.168.0.1", 6991, 0));
+    SharedHandle<UDPTrackerRequest> req2
+      (createAnnounce("192.168.0.1", 6991, 0));
+
+    tr.addRequest(req1);
+    tr.addRequest(req2);
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_CONNECT,
+                         (int)bittorrent::getIntParam(data, 8));
+    int32_t transactionId = bittorrent::getIntParam(data, 12);
+    tr.requestSent(now);
+
+    rv = createErrorReply(data, sizeof(data), transactionId, "error");
+    rv = tr.receiveReply(data, rv, req1->remoteAddr, req1->remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req1->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_TRACKER, req1->error);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req2->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_TRACKER, req2->error);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT(tr.getPendingRequests().empty());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+  }
+  {
+    SharedHandle<UDPTrackerRequest> req1
+      (createAnnounce("192.168.0.1", 6991, 0));
+
+    tr.addRequest(req1);
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((ssize_t)16, rv);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_CONNECT,
+                         (int)bittorrent::getIntParam(data, 8));
+    int32_t transactionId = bittorrent::getIntParam(data, 12);
+    tr.requestSent(now);
+
+    int64_t connectionId = 12345;
+    rv = createConnectReply(data, sizeof(data), connectionId, transactionId);
+    rv = tr.receiveReply(data, rv, req1->remoteAddr, req1->remotePort, now);
+    CPPUNIT_ASSERT_EQUAL(0, (int)rv);
+
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE,
+                         (int)bittorrent::getIntParam(data, 8));
+    transactionId = bittorrent::getIntParam(data, 12);
+    tr.requestSent(now);
+
+    rv = createErrorReply(data, sizeof(data), transactionId, "announce error");
+    rv = tr.receiveReply(data, rv, req1->remoteAddr, req1->remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req1->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_TRACKER, req1->error);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT(tr.getPendingRequests().empty());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+  }
+}
+
+void UDPTrackerClientTest::testTimeout()
+{
+  ssize_t rv;
+  unsigned char data[100];
+  std::string remoteAddr;
+  uint16_t remotePort;
+  Timer now;
+  UDPTrackerClient tr;
+  {
+    SharedHandle<UDPTrackerRequest> req1
+      (createAnnounce("192.168.0.1", 6991, 0));
+    SharedHandle<UDPTrackerRequest> req2
+      (createAnnounce("192.168.0.1", 6991, 0));
+
+    tr.addRequest(req1);
+    tr.addRequest(req2);
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_CONNECT,
+                         (int)bittorrent::getIntParam(data, 8));
+    tr.requestSent(now);
+    now.advance(20);
+    // 15 seconds 1st stage timeout passed
+    tr.handleTimeout(now);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT_EQUAL((size_t)3, tr.getPendingRequests().size());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    // CONNECT request was inserted
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_CONNECT,
+                         (int)bittorrent::getIntParam(data, 8));
+    tr.requestSent(now);
+    now.advance(65);
+    // 60 seconds 2nd stage timeout passed
+    tr.handleTimeout(now);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT(tr.getPendingRequests().empty());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req1->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_TIMEOUT, req1->error);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req2->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_TIMEOUT, req2->error);
+  }
+  {
+    SharedHandle<UDPTrackerRequest> req1
+      (createAnnounce("192.168.0.1", 6991, 0));
+
+    tr.addRequest(req1);
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((ssize_t)16, rv);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_CONNECT,
+                         (int)bittorrent::getIntParam(data, 8));
+    int32_t transactionId = bittorrent::getIntParam(data, 12);
+    tr.requestSent(now);
+
+    int64_t connectionId = 12345;
+    rv = createConnectReply(data, sizeof(data), connectionId, transactionId);
+    rv = tr.receiveReply(data, rv, req1->remoteAddr, req1->remotePort, now);
+    CPPUNIT_ASSERT_EQUAL(0, (int)rv);
+
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE,
+                         (int)bittorrent::getIntParam(data, 8));
+    tr.requestSent(now);
+    now.advance(20);
+    // 15 seconds 1st stage timeout passed
+    tr.handleTimeout(now);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT_EQUAL((size_t)1, tr.getPendingRequests().size());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+
+    rv = tr.createRequest(data, sizeof(data), remoteAddr, remotePort, now);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ACT_ANNOUNCE,
+                         (int)bittorrent::getIntParam(data, 8));
+    tr.requestSent(now);
+    now.advance(65);
+    // 60 seconds 2nd stage timeout passed
+    tr.handleTimeout(now);
+    CPPUNIT_ASSERT(tr.getConnectRequests().empty());
+    CPPUNIT_ASSERT(tr.getPendingRequests().empty());
+    CPPUNIT_ASSERT(tr.getInflightRequests().empty());
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_STA_COMPLETE, req1->state);
+    CPPUNIT_ASSERT_EQUAL((int)UDPT_ERR_TIMEOUT, req1->error);
+  }
+}
+
+} // namespace aria2