Skip to Content.
Sympa Menu

sm-commit - [SM-Commit] GIT changes to master grimoire by Mathieu Lonjaret (fc496ec6c54cceebc517b399fd058cc93d0fc174)

sm-commit AT lists.ibiblio.org

Subject: Source Mage code commit list

List archive

Chronological Thread  
  • From: Mathieu Lonjaret <scm AT sourcemage.org>
  • To: sm-commit AT lists.ibiblio.org
  • Subject: [SM-Commit] GIT changes to master grimoire by Mathieu Lonjaret (fc496ec6c54cceebc517b399fd058cc93d0fc174)
  • Date: Sat, 6 Oct 2007 08:21:15 -0500

GIT changes to master grimoire by Mathieu Lonjaret <lejatorn AT sourcemage.org>:

ftp-libs/libtorrent/BUILD | 4
ftp-libs/libtorrent/CONFIGURE | 2
ftp-libs/libtorrent/DEPENDS | 6
ftp-libs/libtorrent/HISTORY | 8
ftp-libs/libtorrent/PRE_BUILD | 10
ftp-libs/libtorrent/PRE_SUB_DEPENDS | 6
ftp-libs/libtorrent/SUB_DEPENDS | 4
ftp-libs/libtorrent/dht.diff | 5112
++++++++++++++++++++++++++++++++++++
ftp/rtorrent/CONFIGURE | 2
ftp/rtorrent/DEPENDS | 6
ftp/rtorrent/HISTORY | 3
ftp/rtorrent/PRE_BUILD | 7
ftp/rtorrent/dht.diff | 707 ++++
13 files changed, 5869 insertions(+), 8 deletions(-)

New commits:
commit fc496ec6c54cceebc517b399fd058cc93d0fc174
Author: Mathieu Lonjaret <lejatorn AT sourcemage.org>
Commit: Mathieu Lonjaret <lejatorn AT sourcemage.org>

rtorrent: added dht support

commit 74fc0fbca2978a7a79ce00c24c0f06f8956bd0f0
Author: Mathieu Lonjaret <lejatorn AT sourcemage.org>
Commit: Mathieu Lonjaret <lejatorn AT sourcemage.org>

libtorrent: added dht support, openssl optional, cleaned out stuff

diff --git a/ftp-libs/libtorrent/BUILD b/ftp-libs/libtorrent/BUILD
index 211eb18..8d4cfca 100755
--- a/ftp-libs/libtorrent/BUILD
+++ b/ftp-libs/libtorrent/BUILD
@@ -1,7 +1,3 @@
-# fix problems on amd64 with libtool versions (bug #13943)
-./autogen.sh &&
-# fix configure so our CXXFLAGS get used
-sedit 's/test $CXXFLAGS/test -n "$CXXFLAGS"/' configure &&
# remove -O3 from CXXFLAGS on gcc 4.0.x, leads to wrong code -> segfault
if use_gcc ^4; then
CXXFLAGS="${CXXFLAGS/-O3/-Os}"
diff --git a/ftp-libs/libtorrent/CONFIGURE b/ftp-libs/libtorrent/CONFIGURE
new file mode 100755
index 0000000..9543302
--- /dev/null
+++ b/ftp-libs/libtorrent/CONFIGURE
@@ -0,0 +1,2 @@
+config_query DHT_PATCH "Patch to support dht?" y
+
diff --git a/ftp-libs/libtorrent/DEPENDS b/ftp-libs/libtorrent/DEPENDS
index 5480c4d..62afe9d 100755
--- a/ftp-libs/libtorrent/DEPENDS
+++ b/ftp-libs/libtorrent/DEPENDS
@@ -1,4 +1,4 @@
-depends openssl &&
-depends libsigc++3 &&
-depends automake &&
+optional_depends "openssl" "" "--disable-openssl" "Use openssl?" &&
+depends libsigc++3 &&
+depends automake &&
depends libtool
diff --git a/ftp-libs/libtorrent/HISTORY b/ftp-libs/libtorrent/HISTORY
index d578a9d..876a16a 100644
--- a/ftp-libs/libtorrent/HISTORY
+++ b/ftp-libs/libtorrent/HISTORY
@@ -1,3 +1,11 @@
+2007-10-06 Mathieu Lonjaret <lejatorn AT sourcemage.org>
+ * BUILD,PRE_BUILD: moved autogen, removed sedit (not needed anymore)
+
+2007-10-03 Mathieu Lonjaret <lejatorn AT sourcemage.org>
+ * DEPENDS: set openssl to optional
+ * DEPENDS, SUB_DEPENDS, CONFIGURE, PRE_SUB_DEPENDS, PRE_BUILD,
+ * dht.diff: added support for dht
+
2007-09-10 Mathieu Lonjaret <lejatorn AT sourcemage.org>
* DETAILS: update to 0.11.8

diff --git a/ftp-libs/libtorrent/PRE_BUILD b/ftp-libs/libtorrent/PRE_BUILD
new file mode 100755
index 0000000..a56e90b
--- /dev/null
+++ b/ftp-libs/libtorrent/PRE_BUILD
@@ -0,0 +1,10 @@
+default_pre_build &&
+cd ${SOURCE_DIRECTORY} &&
+# fix problems on amd64 with libtool versions (bug #13943)
+./autogen.sh &&
+# dht patch
+if [[ ${DHT_PATCH} != n ]]; then
+ cp ${SPELL_DIRECTORY}/dht.diff ${SOURCE_DIRECTORY}/dht.diff &&
+ patch -p1 < dht.diff
+fi
+
diff --git a/ftp-libs/libtorrent/PRE_SUB_DEPENDS
b/ftp-libs/libtorrent/PRE_SUB_DEPENDS
new file mode 100755
index 0000000..ccf10ca
--- /dev/null
+++ b/ftp-libs/libtorrent/PRE_SUB_DEPENDS
@@ -0,0 +1,6 @@
+case "$THIS_SUB_DEPENDS" in
+ DHT) if [[ $DHT_PATCH == y ]] ; then return 0
+ else return 1
+ fi ;;
+ *) echo "bogus sub_depends: libtorrent $THIS_SUB_DEPENDS"; return 1;;
+esac
diff --git a/ftp-libs/libtorrent/SUB_DEPENDS b/ftp-libs/libtorrent/SUB_DEPENDS
new file mode 100755
index 0000000..c12b35d
--- /dev/null
+++ b/ftp-libs/libtorrent/SUB_DEPENDS
@@ -0,0 +1,4 @@
+case "$THIS_SUB_DEPENDS" in
+ DHT) DHT_PATCH="y" ;;
+ *) echo "bogus sub_depends: libtorrent $THIS_SUB_DEPENDS"; return 1;;
+esac
diff --git a/ftp-libs/libtorrent/dht.diff b/ftp-libs/libtorrent/dht.diff
new file mode 100644
index 0000000..12bac8a
--- /dev/null
+++ b/ftp-libs/libtorrent/dht.diff
@@ -0,0 +1,5112 @@
+Index: libtorrent/scripts/checks.m4
+===================================================================
+--- libtorrent/scripts/checks.m4 (revision 975)
++++ libtorrent/scripts/checks.m4 (working copy)
+@@ -289,7 +289,27 @@
+ ])
+ ])
+
++AC_DEFUN([TORRENT_CHECK_TR1], [
++ AC_LANG_PUSH(C++)
++ AC_MSG_CHECKING(for TR1 support)
+
++ AC_COMPILE_IFELSE(
++ [[#include <tr1/unordered_map>
++ class Foo;
++ typedef std::tr1::unordered_map<Foo*, int> Bar;
++ ]],
++ [
++ AC_MSG_RESULT(yes)
++ AC_DEFINE(HAVE_TR1, 1, Define to 1 if your C++ library supports the
extensions from Technical Report 1)
++ ],
++ [
++ AC_MSG_RESULT(no)
++ ]
++ )
++
++ AC_LANG_POP(C++)
++])
++
+ AC_DEFUN([TORRENT_WITH_FASTCGI], [
+ AC_ARG_WITH(fastcgi,
+ [ --with-fastcgi=PATH Enable FastCGI RPC support. (DO NOT USE)],
+Index: libtorrent/scripts/common.m4
+===================================================================
+--- libtorrent/scripts/common.m4 (revision 975)
++++ libtorrent/scripts/common.m4 (working copy)
+@@ -239,3 +239,18 @@
+ fi
+ ])
+ ])
++
++AC_DEFUN([TORRENT_ENABLE_TR1], [
++ AC_ARG_ENABLE(std_tr1,
++ [ --disable-std_tr1 disable check for support for TR1
[[default=enable]]],
++ [
++ if test "$enableval" = "yes"; then
++ TORRENT_CHECK_TR1()
++ else
++ AC_MSG_CHECKING(for TR1 support)
++ AC_MSG_RESULT(disabled)
++ fi
++ ],[
++ TORRENT_CHECK_TR1()
++ ])
++])
+Index: libtorrent/src/dht/dht_tracker.h
+===================================================================
+--- libtorrent/src/dht/dht_tracker.h (revision 0)
++++ libtorrent/src/dht/dht_tracker.h (revision 0)
+@@ -0,0 +1,84 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_TORRENT_H
++#define LIBTORRENT_DHT_TORRENT_H
++
++#include "globals.h"
++
++#include <vector>
++#include <rak/socket_address.h>
++
++#include "download/download_info.h" // for SocketAddressCompact
++
++namespace torrent {
++
++class Object;
++
++// Container for peers tracked in a torrent.
++
++class DhtTracker : private std::vector<SocketAddressCompact> {
++public:
++ typedef std::vector<SocketAddressCompact> base_type;
++
++ // Maximum number of peers we return for a GET_PEERS query (default
value).
++ // Needs to be small enough so that a packet with a payload of
num_peers*6 bytes
++ // does not need fragmentation. Value chosen so that the size is
approximately
++ // equal to a FIND_NODE reply (8*26 bytes).
++ static const unsigned int max_peers = 32;
++
++ // Maximum number of peers we keep track of. For torrents with more peers,
++ // replace random peers with new announces to avoid excessively large peer
++ // tables for active torrents.
++ static const unsigned int max_size = 128;
++
++ using base_type::empty;
++ using base_type::size;
++
++ void add_peer(const rak::socket_address* sa);
++ std::string get_peers(unsigned int maxPeers = max_peers);
++
++ // Remove old announces from the tracker that have not reannounced for
++ // more than the given number of prune calls.
++ void prune(uint32_t maxAge);
++
++private:
++ std::vector<uint32_t> m_lastSeen;
++};
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_bucket.cc
+===================================================================
+--- libtorrent/src/dht/dht_bucket.cc (revision 0)
++++ libtorrent/src/dht/dht_bucket.cc (revision 0)
+@@ -0,0 +1,191 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include "torrent/exceptions.h"
++
++#include "dht_bucket.h"
++#include "dht_node.h"
++
++namespace torrent {
++
++DhtBucket::DhtBucket(const HashString& begin, const HashString& end) :
++ m_parent(NULL),
++ m_child(NULL),
++
++ m_lastChanged(cachedTime.seconds()),
++
++ m_good(0),
++ m_bad(0),
++
++ m_begin(begin),
++ m_end(end) {
++
++ reserve(num_nodes);
++}
++
++void
++DhtBucket::add_node(DhtNode* n) {
++ push_back(n);
++ touch();
++
++ if (n->is_good())
++ m_good++;
++ else if (n->is_bad())
++ m_bad++;
++}
++
++void
++DhtBucket::remove_node(DhtNode* n) {
++ iterator itr = std::find_if(begin(), end(),
std::bind2nd(std::equal_to<DhtNode*>(), n));
++ if (itr == end())
++ throw internal_error("DhtBucket::remove_node called for node not in
bucket.");
++
++ erase(itr);
++
++ if (n->is_good())
++ m_good--;
++ else if (n->is_bad())
++ m_bad--;
++}
++
++void
++DhtBucket::count() {
++ m_good = std::count_if(begin(), end(), std::mem_fun(&DhtNode::is_good));
++ m_bad = std::count_if(begin(), end(), std::mem_fun(&DhtNode::is_bad));
++}
++
++// Called every 15 minutes for housekeeping.
++void
++DhtBucket::update() {
++ // For now we only update the counts after some nodes have become bad
++ // due to prolonged inactivity.
++ count();
++}
++
++DhtBucket::iterator
++DhtBucket::find_replacement_candidate(bool onlyOldest) {
++ iterator oldest = end();
++ unsigned int oldestTime = std::numeric_limits<unsigned int>::max();
++
++ for (iterator itr = begin(); itr != end(); ++itr) {
++ if ((*itr)->is_bad() && !onlyOldest)
++ return itr;
++
++ if ((*itr)->last_seen() < oldestTime) {
++ oldestTime = (*itr)->last_seen();
++ oldest = itr;
++ }
++ }
++
++ return oldest;
++}
++
++void
++DhtBucket::get_mid_point(HashString* middle) const {
++ *middle = m_end;
++
++ for (unsigned int i=0; i<m_begin.size(); i++)
++ if (m_begin[i] != m_end[i]) {
++ (*middle)[i] = ((uint8_t)m_begin[i] + (uint8_t)m_end[i]) / 2;
++ break;
++ }
++}
++
++void
++DhtBucket::get_random_id(HashString* rand_id) const {
++
++ // Generate a random ID between m_begin and m_end.
++ // Since m_end - m_begin = 2^n - 1, we can do a bitwise AND operation.
++ for (unsigned int i=0; i<(*rand_id).size(); i++)
++ (*rand_id)[i] = m_begin[i] + (random() & (m_end[i] - m_begin[i]));
++
++#ifdef USE_EXTRA_DEBUG
++ if (!is_in_range(*rand_id))
++ throw internal_error("DhtBucket::get_random_id generated an
out-of-range ID.");
++#endif
++}
++
++DhtBucket*
++DhtBucket::split(const HashString& id) {
++ HashString mid_range;
++ get_mid_point(&mid_range);
++
++ DhtBucket* other = new DhtBucket(m_begin, mid_range);
++
++ // Set m_begin = mid_range + 1
++ int carry = 1;
++ for (unsigned int i = mid_range.size(); i>0; i--) {
++ unsigned int sum = (uint8_t)mid_range[i-1] + carry;
++ m_begin[i-1] = (uint8_t)sum;
++ carry = sum >> 8;
++ }
++
++ // Move nodes over to other bucket if they fall in its range, then
++ // delete them from this one.
++ iterator split = std::partition(begin(), end(),
std::bind2nd(std::mem_fun(&DhtNode::is_in_range), this));
++ other->insert(other->end(), split, end());
++ std::for_each(other->begin(), other->end(),
std::bind2nd(std::mem_fun(&DhtNode::set_bucket), other));
++ erase(split, end());
++
++ other->set_time(m_lastChanged);
++ other->count();
++
++ count();
++
++ // Maintain child (adjacent narrower bucket) and parent (adjacent wider
bucket)
++ // so that given router ID is in child.
++ if (other->is_in_range(id)) {
++ // Make other become our new child.
++ m_child = other;
++ other->m_parent = this;
++
++ } else {
++ // We become other's child, other becomes our parent's child.
++ if (parent()) {
++ parent()->m_child = other;
++ other->m_parent = parent();
++ }
++
++ m_parent = other;
++ other->m_child = this;
++ }
++
++ return other;
++}
++
++}
+Index: libtorrent/src/dht/dht_bucket.h
+===================================================================
+--- libtorrent/src/dht/dht_bucket.h (revision 0)
++++ libtorrent/src/dht/dht_bucket.h (revision 0)
+@@ -0,0 +1,183 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_BUCKET_H
++#define LIBTORRENT_DHT_BUCKET_H
++
++#include <list>
++
++#include "globals.h"
++
++#include "torrent/hash_string.h"
++
++namespace torrent {
++
++class DhtNode;
++
++// A container holding a small number of nodes that fall in a given binary
++// partition of the 160-bit ID space (i.e. the range ID1..ID2 where
ID2-ID1+1 is
++// a power of 2.)
++class DhtBucket : private std::vector<DhtNode*> {
++public:
++ static const unsigned int num_nodes = 8;
++
++ typedef std::vector<DhtNode*> base_type;
++
++ using base_type::const_iterator;
++ using base_type::iterator;
++
++ using base_type::begin;
++ using base_type::end;
++ using base_type::size;
++ using base_type::empty;
++
++ DhtBucket(const HashString& begin, const HashString& end);
++
++ // Add new node. Does NOT set node's bucket automatically (to allow
adding a
++ // node to multiple buckets, with only one "main" bucket.)
++ void add_node(DhtNode* n);
++
++ void remove_node(DhtNode* n);
++
++ // Bucket's ID range functions.
++ const HashString& id_range_begin() const { return
m_begin; }
++ HashString& id_range_begin() { return
m_begin; }
++ const HashString& id_range_end() const { return
m_end; }
++ HashString& id_range_end() { return
m_end; }
++
++ bool is_in_range(const HashString& id) const { return
m_begin <= id && id <= m_end; }
++
++ // Find middle or random ID in bucket.
++ void get_mid_point(HashString* middle) const;
++ void get_random_id(HashString* rand_id) const;
++
++ // Node counts and bucket stats.
++ bool is_full() const { return
size() >= num_nodes; }
++ bool has_space() const { return
!is_full() || num_bad() > 0; }
++ unsigned int num_good() const { return
m_good; }
++ unsigned int num_bad() const { return
m_bad; }
++
++ unsigned int age() const { return
cachedTime.seconds() - m_lastChanged; }
++ void touch() {
m_lastChanged = cachedTime.seconds(); }
++ void set_time(int time) {
m_lastChanged = time; }
++
++ // Called every 15 minutes after updating nodes.
++ void update();
++
++ // Return candidate for replacement (a bad node or the oldest node); may
++ // return end() unless has_space() is true.
++ iterator find_replacement_candidate(bool onlyOldest = false);
++
++ // Split the bucket in two and redistribute nodes. Returned bucket is the
++ // lower half, "this" bucket keeps the upper half. Sets parent/child so
++ // that the bucket the given ID falls in is the child.
++ DhtBucket* split(const HashString& id);
++
++ // Parent and child buckets. Parent is the adjacent bucket with double
the
++ // ID width, child the adjacent bucket with half the width (except the
very
++ // last child which has the same width.)
++ DhtBucket* parent() const { return
m_parent; }
++ DhtBucket* child() const { return
m_child; }
++
++ // Called by the DhtNode on its bucket to update good/bad node counts.
++ void node_now_good(bool was_bad);
++ void node_now_bad(bool was_good);
++
++private:
++ void count();
++
++ DhtBucket* m_parent;
++ DhtBucket* m_child;
++
++ unsigned int m_lastChanged;
++
++ unsigned int m_good;
++ unsigned int m_bad;
++
++ // These are 40 bytes together, so might as well put them last.
++ HashString m_begin;
++ HashString m_end;
++};
++
++// Helper class to recursively follow a chain of buckets. It first recurses
++// into the bucket's children since they are by definition closer to the
bucket,
++// then continues with the bucket's parents.
++class DhtBucketChain {
++public:
++ DhtBucketChain(const DhtBucket* b) : m_restart(b), m_cur(b) { }
++
++ const DhtBucket* bucket() { return
m_cur; }
++ const DhtBucket* next();
++
++private:
++ const DhtBucket* m_restart;
++ const DhtBucket* m_cur;
++};
++
++inline void
++DhtBucket::node_now_good(bool was_bad) {
++ m_bad -= was_bad;
++ m_good++;
++}
++
++inline void
++DhtBucket::node_now_bad(bool was_good) {
++ m_good -= was_good;
++ m_bad++;
++}
++
++inline const DhtBucket*
++DhtBucketChain::next() {
++ // m_restart is clear when we're done recursing into the children and
++ // follow the parents instead.
++ if (m_restart == NULL) {
++ m_cur = m_cur->parent();
++
++ } else {
++ m_cur = m_cur->child();
++
++ if (!m_cur) {
++ m_cur = m_restart->parent();
++ m_restart = NULL;
++ }
++ }
++
++ return m_cur;
++}
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_router.cc
+===================================================================
+--- libtorrent/src/dht/dht_router.cc (revision 0)
++++ libtorrent/src/dht/dht_router.cc (revision 0)
+@@ -0,0 +1,646 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++#include "globals.h"
++
++#include <sstream>
++#include <rak/functional.h>
++#include <rak/string_manip.h>
++
++#include "torrent/dht_manager.h"
++#include "torrent/exceptions.h"
++#include "utils/sha1.h"
++#include "manager.h"
++
++#include "dht_bucket.h"
++#include "dht_router.h"
++#include "dht_tracker.h"
++#include "dht_transaction.h"
++
++namespace torrent {
++
++HashString DhtRouter::zero_id;
++
++DhtRouter::DhtRouter(const Object& cache, const rak::socket_address* sa) :
++ DhtNode(zero_id, sa), // actual ID is set later
++ m_server(this),
++ m_contacts(NULL),
++ m_numRefresh(0),
++ m_curToken(random()),
++ m_prevToken(random()) {
++
++ HashString ones_id;
++
++ zero_id.clear();
++ ones_id.clear(0xFF);
++
++ if (cache.has_key("self_id")) {
++ assign(cache.get_key_string("self_id").c_str());
++
++ } else {
++ long buffer[size_data];
++
++ for (long* itr = buffer; itr != buffer + size_data; ++itr)
++ *itr = random();
++
++ Sha1 sha;
++ sha.init();
++ sha.update(buffer, sizeof(buffer));
++ sha.final_c(data());
++ }
++
++ set_bucket(new DhtBucket(zero_id, ones_id));
++ m_routingTable.insert(std::make_pair(bucket()->id_range_end(), bucket()));
++
++ if (cache.has_key("nodes")) {
++ Object::map_type nodes = cache.get_key_map("nodes");
++
++ for (Object::map_type::const_iterator itr = nodes.begin(); itr !=
nodes.end(); ++itr) {
++ if (itr->first.length() != HashString::size_data)
++ throw bencode_error("Loading cache: Invalid node hash.");
++
++ add_node_to_bucket(m_nodes.insert(new DhtNode(itr->first,
itr->second)));
++ }
++ }
++
++ if (m_nodes.size() < num_bootstrap_complete)
++ m_contacts = new std::list<rak::socket_address>;
++}
++
++DhtRouter::~DhtRouter() {
++ stop();
++ std::for_each(m_routingTable.begin(), m_routingTable.end(),
rak::on(rak::mem_ref(&DhtBucketList::value_type::second),
rak::call_delete<DhtBucket>()));
++ std::for_each(m_trackers.begin(), m_trackers.end(),
rak::on(rak::mem_ref(&DhtTrackerList::value_type::second),
rak::call_delete<DhtTracker>()));
++ std::for_each(m_nodes.begin(), m_nodes.end(),
rak::on(rak::mem_ref(&DhtNodeList::value_type::second),
rak::call_delete<DhtNode>()));
++}
++
++void
++DhtRouter::start(int port) {
++ m_server.start(port);
++
++ // Set timeout slot and schedule it to be called immediately for initial
bootstrapping if necessary.
++ m_taskTimeout.set_slot(rak::mem_fn(this,
&DhtRouter::receive_timeout_bootstrap));
++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(1)).round_seconds());
++}
++
++void
++DhtRouter::stop() {
++ priority_queue_erase(&taskScheduler, &m_taskTimeout);
++ m_server.stop();
++}
++
++// Start a DHT get_peers and announce_peer request.
++void
++DhtRouter::announce(DownloadInfo* info, TrackerDht* tracker) {
++ m_server.announce(*find_bucket(info->hash())->second, info->hash(),
tracker);
++}
++
++// Cancel any running requests from the given tracker.
++// If info is not NULL, only cancel those requests.
++void
++DhtRouter::cancel_announce(DownloadInfo* info, const TrackerDht* tracker) {
++ m_server.cancel_announce(info, tracker);
++}
++
++DhtTracker*
++DhtRouter::get_tracker(const HashString& hash, bool create) {
++ DhtTrackerList::accessor itr = m_trackers.find(hash);
++
++ if (itr == m_trackers.end()) {
++ if (!create)
++ return NULL;
++
++ std::pair<DhtTrackerList::accessor, bool> res =
m_trackers.insert(std::make_pair(hash, new DhtTracker()));
++
++ if (!res.second)
++ throw internal_error("DhtRouter::get_tracker did not actually insert
tracker.");
++
++ return res.first.tracker();
++ }
++
++ return itr.tracker();
++}
++
++bool
++DhtRouter::want_node(const HashString& id) {
++ // We don't want to add ourself. Also, too many broken implementations
++ // advertise an ID of 0, which causes collisions, so reject that.
++ if (id == this->id() || id == zero_id)
++ return false;
++
++ // We are always interested in more nodes for our own bucket (causing it
++ // to be split if full); in other buckets only if there's space.
++ DhtBucket* b = find_bucket(id)->second;
++ return b == bucket() || b->has_space();
++}
++
++DhtNode*
++DhtRouter::get_node(const HashString& id) {
++ DhtNodeList::accessor itr = m_nodes.find(&id);
++
++ if (itr == m_nodes.end()) {
++ if (id == this->id())
++ return this;
++ else
++ return NULL;
++ }
++
++ return itr.node();
++}
++
++DhtRouter::DhtBucketList::iterator
++DhtRouter::find_bucket(const HashString& id) {
++ DhtBucketList::iterator itr = m_routingTable.upper_bound(id);
++
++#ifdef USE_EXTRA_DEBUG
++ if (itr == m_routingTable.end())
++ throw internal_error("DHT Buckets not covering entire ID space.");
++
++ if (!itr->second->is_in_range(id))
++ throw internal_error("DhtRouter::find_bucket,
m_routingTable.upper_bound did not find correct bucket.");
++#endif
++
++ return itr;
++}
++
++void
++DhtRouter::contact(const rak::socket_address* sa, bool external) {
++ // For non-external nodes, e.g. those from an explict add_node command
++ // or found via the BT PORT message, contact them immediately if possible.
++ if (!external) {
++ if (is_active())
++ m_server.ping(zero_id, sa);
++
++ } else {
++ // Externally obtained nodes are added to the contact list, but only if
++ // we're still bootstrapping. We don't contact external nodes after
that.
++ if (m_contacts != NULL)
++ m_contacts->push_back(*sa);
++ }
++}
++
++// Received a query from the given node. If it has previously replied
++// to one of our queries, consider it alive and update the bucket mtime,
++// otherwise if we could use it in a bucket, try contacting it.
++DhtNode*
++DhtRouter::node_queried(const HashString& id, const rak::socket_address*
sa) {
++ DhtNode* node = get_node(id);
++
++ if (node == NULL) {
++ if (want_node(id))
++ m_server.ping(id, sa);
++
++ return NULL;
++ }
++
++ // If we know the ID but the address is different, don't set the original
node
++ // active, but neither use this new address to prevent rogue nodes from
polluting
++ // our routing table with fake source addresses.
++ if (node->address()->sa_inet()->address_n() != sa->sa_inet()->address_n())
++ return NULL;
++
++ node->queried();
++ if (node->is_good())
++ node->bucket()->touch();
++
++ return node;
++}
++
++// Received a reply from a node we queried.
++// Check that it matches the information we have, set that it has replied
++// and update the bucket mtime.
++DhtNode*
++DhtRouter::node_replied(const HashString& id, const rak::socket_address*
sa) {
++ DhtNode* node = get_node(id);
++
++ if (node == NULL) {
++ // New node, create it. It's a good node (it replied!) so add it to a
bucket.
++ if (id == this->id() || id == zero_id)
++ return NULL;
++
++ node = m_nodes.insert(new DhtNode(id, sa));
++
++ if (!add_node_to_bucket(node)) // deletes the node if it fails
++ return NULL;
++ }
++
++ if (node->address()->sa_inet()->address_n() != sa->sa_inet()->address_n())
++ return NULL;
++
++ node->replied();
++ node->bucket()->touch();
++
++ return node;
++}
++
++// A node has not replied to one of our queries.
++DhtNode*
++DhtRouter::node_inactive(const HashString& id, const rak::socket_address*
sa) {
++ DhtNodeList::accessor itr = m_nodes.find(&id);
++
++ // If not found add it to some blacklist so we won't try contacting it
again immediately?
++ if (itr == m_nodes.end())
++ return NULL;
++
++ // Check source address. Normally node_inactive is called if we DON'T
receive a reply,
++ // however it can also be called if a node replied with an malformed
response packet,
++ // so check that the address matches so that a rogue node cannot cause
other nodes
++ // to be considered bad by sending malformed packets.
++ if (itr.node()->address()->sa_inet()->address_n() !=
sa->sa_inet()->address_n())
++ return NULL;
++
++ itr.node()->inactive();
++
++ // Old node age normally implies no replies for many consecutive queries,
however
++ // after loading the node cache after a day or more we want to give each
node a few
++ // chances to reply again instead of removing all nodes instantly.
++ if (itr.node()->is_bad() && itr.node()->age() >= timeout_remove_node) {
++ delete_node(itr);
++ return NULL;
++ }
++
++ return itr.node();
++}
++
++// We sent a query to the given node ID, but received a reply from a
different
++// node ID, that means the address of the original ID is invalid now.
++void
++DhtRouter::node_invalid(const HashString& id) {
++ DhtNode* node = get_node(id);
++
++ if (node == NULL || node == this)
++ return;
++
++ delete_node(m_nodes.find(&node->id()));
++}
++
++char*
++DhtRouter::store_closest_nodes(const HashString& id, char* buffer, char*
bufferEnd) {
++ DhtBucketChain chain(find_bucket(id)->second);
++
++ do {
++ for (DhtBucket::const_iterator itr = chain.bucket()->begin(); itr !=
chain.bucket()->end(); ++itr) {
++ if (!(*itr)->is_bad()) {
++ buffer = (*itr)->store_compact(buffer);
++
++ if (buffer > bufferEnd)
++ throw internal_error("DhtRouter::store_closest_nodes wrote past
buffer end.");
++
++ if (buffer == bufferEnd)
++ break;
++ }
++ }
++ } while (buffer != bufferEnd && chain.next());
++
++ return buffer;
++}
++
++Object*
++DhtRouter::store_cache(Object* container) const {
++ container->insert_key("self_id", str());
++
++ // Insert all nodes.
++ Object& nodes = container->insert_key("nodes", Object(Object::TYPE_MAP));
++ for (DhtNodeList::const_accessor itr = m_nodes.begin(); itr !=
m_nodes.end(); ++itr) {
++ if (!itr.node()->is_bad())
++ itr.node()->store_cache(&nodes.insert_key(itr.id().str(),
Object(Object::TYPE_MAP)));
++ }
++
++ return container;
++}
++
++DhtManager::statistics_type
++DhtRouter::get_statistics() const {
++ DhtManager::statistics_type stats(*m_server.upload_throttle()->rate(),
*m_server.download_throttle()->rate());
++
++ if (!m_server.is_active())
++ stats.cycle = 0;
++ else if (m_numRefresh < 2) // still bootstrapping
++ stats.cycle = 1;
++ else
++ stats.cycle = m_numRefresh;
++
++ stats.queries_received = m_server.queries_received();
++ stats.queries_sent = m_server.queries_sent();
++ stats.replies_received = m_server.replies_received();
++
++ stats.num_nodes = m_nodes.size();
++ stats.num_buckets = m_routingTable.size();
++
++ stats.num_peers = 0;
++ stats.max_peers = 0;
++ stats.num_trackers = m_trackers.size();
++
++ for (DhtTrackerList::const_accessor itr = m_trackers.begin(); itr !=
m_trackers.end(); ++itr) {
++ unsigned int peers = itr.tracker()->size();
++ stats.num_peers += peers;
++ stats.max_peers = std::max(peers, stats.max_peers);
++ }
++
++ return stats;
++}
++
++void
++DhtRouter::receive_timeout_bootstrap() {
++ // If we're still bootstrapping, restart the process every 60 seconds
until
++ // we have enough nodes in our routing table. After we have 32 nodes, we
switch
++ // to a less aggressive non-bootstrap mode of collecting nodes that
contact us
++ // and through doing normal torrent announces.
++ if (m_nodes.size() < num_bootstrap_complete) {
++ if (m_contacts == NULL)
++ throw internal_error("DhtRouter::receive_timeout_bootstrap called
without contact list.");
++
++ if (!m_nodes.empty() || !m_contacts->empty())
++ bootstrap();
++
++ // Retry in 60 seconds.
++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(timeout_bootstrap_retry)).round_seconds());
++ m_numRefresh = 1; // still bootstrapping
++
++ } else {
++ // We won't be needing external contacts after this.
++ if (m_contacts != NULL) {
++ delete m_contacts;
++ m_contacts = NULL;
++ }
++
++ m_taskTimeout.set_slot(rak::mem_fn(this, &DhtRouter::receive_timeout));
++
++ if (!m_numRefresh) {
++ // If we're still in the startup, do the usual refreshing too.
++ receive_timeout();
++
++ } else {
++ // Otherwise just set the 15 minute timer.
++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(timeout_update)).round_seconds());
++ }
++
++ m_numRefresh = 2;
++ }
++}
++
++void
++DhtRouter::receive_timeout() {
++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(timeout_update)).round_seconds());
++
++ m_prevToken = m_curToken;
++ m_curToken = random();
++
++ // Do some periodic accounting, refreshing buckets and marking
++ // bad nodes.
++
++ // Update nodes.
++ for (DhtNodeList::accessor itr = m_nodes.begin(); itr != m_nodes.end();
++itr) {
++ if (!itr.node()->bucket())
++ throw internal_error("DhtRouter::receive_timeout has node without
bucket.");
++
++ itr.node()->update();
++
++ // Try contacting nodes we haven't received anything from for a while.
++ // Don't contact repeatedly unresponsive nodes; we keep them in case
they
++ // do send a query, until we find a better node. However, give it a last
++ // chance just before deleting it.
++ if (itr.node()->is_questionable() && (!itr.node()->is_bad() ||
itr.node()->age() >= timeout_remove_node))
++ m_server.ping(itr.node()->id(), itr.node()->address());
++ }
++
++ // If bucket isn't full yet or hasn't received replies/queries from
++ // its nodes for a while, try to find new nodes now.
++ for (DhtBucketList::const_iterator itr = m_routingTable.begin(); itr !=
m_routingTable.end(); ++itr) {
++ itr->second->update();
++
++ if (!itr->second->is_full() || itr->second->age() >
timeout_bucket_bootstrap)
++ bootstrap_bucket(itr->second);
++ }
++
++ // Remove old peers and empty torrents from the tracker.
++ for (DhtTrackerList::accessor itr = m_trackers.begin(); itr !=
m_trackers.end(); ) {
++ itr.tracker()->prune(timeout_peer_announce);
++
++ if (itr.tracker()->empty()) {
++ delete itr.tracker();
++ m_trackers.erase(itr++);
++
++ } else {
++ ++itr;
++ }
++ }
++
++ m_server.update();
++
++ m_numRefresh++;
++}
++
++void
++DhtRouter::generate_token(const rak::socket_address* sa, int token, char
buffer[20]) {
++ Sha1 sha;
++ uint32_t key = sa->sa_inet()->address_n();
++
++ sha.init();
++ sha.update(&token, sizeof(token));
++ sha.update(&key, 4);
++ sha.final_c(buffer);
++}
++
++std::string
++DhtRouter::make_token(const rak::socket_address* sa) {
++ char token[20];
++
++ generate_token(sa, m_curToken, token);
++
++ return std::string(token, size_token);
++}
++
++bool
++DhtRouter::token_valid(const std::string& token, const rak::socket_address*
sa) {
++ if (token.length() != size_token)
++ return false;
++
++ // Compare given token to the reference token.
++ char reference[20];
++
++ // First try current token.
++ generate_token(sa, m_curToken, reference);
++
++ if (std::memcmp(reference, token.c_str(), size_token) == 0)
++ return true;
++
++ // If token recently changed, some clients may be using the older one.
++ // That way a token is valid for 15-30 minutes, instead of 0-15.
++ generate_token(sa, m_prevToken, reference);
++
++ return std::memcmp(reference, token.c_str(), size_token) == 0;
++}
++
++DhtNode*
++DhtRouter::find_node(const rak::socket_address* sa) {
++ for (DhtNodeList::accessor itr = m_nodes.begin(); itr != m_nodes.end();
++itr)
++ if (itr.node()->address()->sa_inet()->address_n() ==
sa->sa_inet()->address_n())
++ return itr.node();
++
++ return NULL;
++}
++
++DhtRouter::DhtBucketList::iterator
++DhtRouter::split_bucket(const DhtBucketList::iterator& itr, DhtNode* node) {
++ // Split bucket. Current bucket keeps the upper half thus keeping the
++ // map key valid, new bucket is the lower half of the original bucket.
++ DhtBucket* newBucket = itr->second->split(id());
++
++ // If our bucket has a child now (the new bucket), move ourself into it.
++ if (bucket()->child() != NULL)
++ set_bucket(bucket()->child());
++
++ if (!bucket()->is_in_range(id()))
++ throw internal_error("DhtRouter::split_bucket router ID ended up in
wrong bucket.");
++
++ // Insert new bucket with iterator hint = just before current bucket.
++ DhtBucketList::iterator other = m_routingTable.insert(itr,
std::make_pair(newBucket->id_range_end(), newBucket));
++
++ // Check that the bucket we're not adding the node to isn't empty.
++ if (other->second->is_in_range(node->id())) {
++ if (itr->second->empty())
++ bootstrap_bucket(itr->second);
++
++ } else {
++ if (other->second->empty())
++ bootstrap_bucket(other->second);
++
++ other = itr;
++ }
++
++ return other;
++}
++
++bool
++DhtRouter::add_node_to_bucket(DhtNode* node) {
++ DhtBucketList::iterator itr = find_bucket(node->id());
++
++ while (itr->second->is_full()) {
++ // Bucket is full. If there are any bad nodes, remove the oldest.
++ DhtBucket::iterator nodeItr = itr->second->find_replacement_candidate();
++ if (nodeItr == itr->second->end())
++ throw internal_error("DhtBucket::find_candidate returned no node.");
++
++ if ((*nodeItr)->is_bad()) {
++ delete_node(m_nodes.find(&(*nodeItr)->id()));
++
++ } else {
++ // Bucket is full of good nodes; if our own ID falls in
++ // range then split the bucket else discard new node.
++ if (itr->second != bucket()) {
++ delete_node(m_nodes.find(&node->id()));
++ return false;
++ }
++
++ itr = split_bucket(itr, node);
++ }
++ }
++
++ itr->second->add_node(node);
++ node->set_bucket(itr->second);
++ return true;
++}
++
++void
++DhtRouter::delete_node(const DhtNodeList::accessor& itr) {
++ if (itr == m_nodes.end())
++ throw internal_error("DhtRouter::delete_node called with invalid
iterator.");
++
++ if (itr.node()->bucket() != NULL)
++ itr.node()->bucket()->remove_node(itr.node());
++
++ delete itr.node();
++
++ m_nodes.erase(itr);
++}
++
++void
++DhtRouter::bootstrap() {
++ // Contact up to 8 nodes from the contact list.
++ for (int count = 0; count < 8 && !m_contacts->empty(); count++) {
++ if (!find_node(&m_contacts->back()))
++ m_server.ping(zero_id, &m_contacts->back());
++
++ m_contacts->pop_back();
++ }
++
++ // Abort unless we already found some nodes for a search.
++ if (m_nodes.empty())
++ return;
++
++ bootstrap_bucket(bucket());
++
++ // Aggressively ping all questionable nodes in our own bucket to weed
++ // out bad nodes as early as possible and make room for fresh nodes.
++ for (DhtBucket::iterator itr = bucket()->begin(); itr != bucket()->end();
++itr)
++ if (!(*itr)->is_good())
++ m_server.ping((*itr)->id(), (*itr)->address());
++
++ // Also bootstrap a random bucket, if there are others.
++ if (m_routingTable.size() < 2)
++ return;
++
++ DhtBucketList::iterator itr = m_routingTable.begin();
++ std::advance(itr, random() % m_routingTable.size());
++
++ if (itr->second != bucket() && itr != m_routingTable.end())
++ bootstrap_bucket(itr->second);
++}
++
++void
++DhtRouter::bootstrap_bucket(const DhtBucket* bucket) {
++ if (!m_server.is_active())
++ return;
++
++ // Do a search for a random ID, or the ID adjacent to our
++ // own when bootstrapping our own bucket. We don't search for
++ // our own exact ID to avoid receiving only our own node info
++ // instead of closest nodes, from nodes that know us already.
++ HashString contactId;
++
++ if (bucket == this->bucket()) {
++ contactId = id();
++ contactId[contactId.size() - 1] ^= 1;
++ } else {
++ bucket->get_random_id(&contactId);
++ }
++
++ m_server.find_node(*bucket, contactId);
++}
++
++}
+Index: libtorrent/src/dht/dht_router.h
+===================================================================
+--- libtorrent/src/dht/dht_router.h (revision 0)
++++ libtorrent/src/dht/dht_router.h (revision 0)
+@@ -0,0 +1,174 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_ROUTER_H
++#define LIBTORRENT_DHT_ROUTER_H
++
++#include <rak/priority_queue_default.h>
++#include <rak/socket_address.h>
++
++#include "torrent/dht_manager.h"
++#include "torrent/hash_string.h"
++#include "torrent/object.h"
++
++#include "dht_node.h"
++#include "dht_hash_map.h"
++#include "dht_server.h"
++
++namespace torrent {
++
++class DhtBucket;
++class DhtTracker;
++class TrackerDht;
++
++// Main DHT class, maintains the routing table of known nodes and talks to
the
++// DhtServer object that handles the actual communication.
++
++class DhtRouter : public DhtNode {
++public:
++ // How many bytes to return and verify from the 20-byte SHA token.
++ static const unsigned int size_token = 8;
++
++ static const unsigned int timeout_bootstrap_retry = 60; //
Retry initial bootstrapping every minute.
++ static const unsigned int timeout_update = 15 * 60; //
Regular housekeeping updates every 15 minutes.
++ static const unsigned int timeout_bucket_bootstrap = 15 * 60; //
Bootstrap idle buckets after 15 minutes.
++ static const unsigned int timeout_remove_node = 4 * 60 * 60; //
Remove unresponsive nodes after 4 hours.
++ static const unsigned int timeout_peer_announce = 30 * 60; //
Remove peers which haven't reannounced for 30 minutes.
++
++ // A node ID of all zero.
++ static HashString zero_id;
++
++ DhtRouter(const Object& cache, const rak::socket_address* sa);
++ ~DhtRouter();
++
++ // Start and stop the router. This starts/stops the UDP server as well.
++ void start(int port);
++ void stop();
++
++ bool is_active() { return
m_server.is_active(); }
++
++ // Find peers for given download and announce ourselves.
++ void announce(DownloadInfo* info, TrackerDht* tracker);
++
++ // Cancel any pending transactions related to the given download (or all
if NULL).
++ void cancel_announce(DownloadInfo* info, const TrackerDht*
tracker);
++
++ // Retrieve tracked torrent for the hash.
++ // Returns NULL if not tracking the torrent unless create is true.
++ DhtTracker* get_tracker(const HashString& hash, bool create);
++
++ // Check if we are interested in inserting a new node of the given ID
++ // into our table (i.e. if we have space or bad nodes in the
corresponding bucket).
++ bool want_node(const HashString& id);
++
++ // Contact the given node, or add it to the list of potential contacts
++ // if it was obtained externally from a .torrent file for example.
++ void contact(const rak::socket_address* sa, bool external);
++
++ // Retrieve node of given ID in constant time. Return NULL if not found,
unless
++ // it's our own ID in which case it returns the DhtRouter object.
++ DhtNode* get_node(const HashString& id);
++
++ // Search for node with given address in O(n), disregarding the port.
++ DhtNode* find_node(const rak::socket_address* sa);
++
++ // Whenever a node queries us, replies, or is confirmed inactive (no
reply) or
++ // invalid (reply with wrong ID), we need to update its status.
++ DhtNode* node_queried(const HashString& id, const
rak::socket_address* sa);
++ DhtNode* node_replied(const HashString& id, const
rak::socket_address* sa);
++ DhtNode* node_inactive(const HashString& id, const
rak::socket_address* sa);
++ void node_invalid(const HashString& id);
++
++ // Store compact node information (26 bytes) for nodes closest to the
++ // given ID in the given buffer, return new buffer end.
++ char* store_closest_nodes(const HashString& id, char*
buffer, char* bufferEnd);
++
++ // Store DHT cache in the given container.
++ Object* store_cache(Object* container) const;
++
++ // Create and verify a token. Tokens are valid between 15-30 minutes from
creation.
++ std::string make_token(const rak::socket_address* sa);
++ bool token_valid(const std::string& token, const
rak::socket_address* sa);
++
++ DhtManager::statistics_type get_statistics() const;
++ void reset_statistics() {
m_server.reset_statistics(); }
++
++ DhtServer* server() { return
&m_server; }
++ DhtTrackerList* trackers() { return
&m_trackers; }
++
++private:
++ // Number of nodes we need to consider the bootstrap process complete.
++ static const unsigned int num_bootstrap_complete = 32;
++
++ typedef std::map<const HashString, DhtBucket*> DhtBucketList;
++
++ DhtBucketList::iterator find_bucket(const HashString& id);
++
++ bool add_node_to_bucket(DhtNode* node);
++ void delete_node(const DhtNodeList::accessor& itr);
++
++ DhtBucketList::iterator split_bucket(const DhtBucketList::iterator& itr,
DhtNode* node);
++
++ void bootstrap();
++ void bootstrap_bucket(const DhtBucket* bucket);
++
++ void receive_timeout();
++ void receive_timeout_bootstrap();
++
++ // buffer needs to hold an SHA1 hash (20 bytes), not just the token (8
bytes)
++ void generate_token(const rak::socket_address* sa, int
token, char buffer[20]);
++
++ rak::priority_item m_taskTimeout;
++
++ DhtServer m_server;
++ DhtNodeList m_nodes;
++ DhtBucketList m_routingTable;
++ DhtTrackerList m_trackers;
++
++ std::list<rak::socket_address>* m_contacts;
++
++ int m_numRefresh;
++
++ bool m_networkUp;
++
++ // Secret keys used for generating announce tokens.
++ int m_curToken;
++ int m_prevToken;
++};
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_hash_map.h
+===================================================================
+--- libtorrent/src/dht/dht_hash_map.h (revision 0)
++++ libtorrent/src/dht/dht_hash_map.h (revision 0)
+@@ -0,0 +1,168 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_HASH_MAP_H
++#define LIBTORRENT_DHT_HASH_MAP_H
++
++#include "config.h"
++
++#if HAVE_TR1
++#include <tr1/unordered_map>
++#else
++#include <map>
++#endif
++
++#include "torrent/hash_string.h"
++
++#include "dht_node.h"
++#include "dht_tracker.h"
++
++namespace torrent {
++
++#if HAVE_TR1
++// Hash functions for HashString keys, and dereferencing HashString
pointers.
++// We use the last n bits of the 160-bit ID hash, since in sub-buckets the
++// first few bits are all identical.
++struct hashstring_ptr_hash : public std::unary_function<const HashString*,
size_t> {
++ size_t operator () (const HashString* n) const
++ { return *(size_t*)(n->data() + n->size() - sizeof(size_t)); }
++};
++
++struct hashstring_hash : public std::unary_function<HashString, size_t> {
++ size_t operator () (const HashString& n) const
++ { return *(size_t*)(n.data() + n.size() - sizeof(size_t)); }
++};
++
++// Compare HashString pointers by dereferencing them.
++struct hashstring_ptr_equal : public std::binary_function<const
HashString*, const HashString*, bool> {
++ size_t operator () (const HashString* one, const HashString* two) const
++ { return *one == *two; }
++};
++
++class DhtNodeList : public std::tr1::unordered_map<const HashString*,
DhtNode*, hashstring_ptr_hash, hashstring_ptr_equal> {
++public:
++ typedef std::tr1::unordered_map<const HashString*, DhtNode*,
hashstring_ptr_hash, hashstring_ptr_equal> base_type;
++
++ // Define accessor iterator with more convenient access to the key and
++ // element values. Allows changing the map definition more easily if
needed.
++ template<typename T>
++ struct accessor_wrapper : public T {
++ accessor_wrapper(const T& itr) : T(itr) { }
++
++ const HashString& id() const { return *(**this).first; }
++ DhtNode* node() const { return (**this).second; }
++ };
++
++ typedef accessor_wrapper<const_iterator> const_accessor;
++ typedef accessor_wrapper<iterator> accessor;
++
++ DhtNode* insert(DhtNode* n);
++
++};
++
++class DhtTrackerList : public std::tr1::unordered_map<HashString,
DhtTracker*, hashstring_hash> {
++public:
++ typedef std::tr1::unordered_map<HashString, DhtTracker*, hashstring_hash>
base_type;
++
++ template<typename T>
++ struct accessor_wrapper : public T {
++ accessor_wrapper(const T& itr) : T(itr) { }
++
++ const HashString& id() const { return (**this).first; }
++ DhtTracker* tracker() const { return (**this).second; }
++ };
++
++ typedef accessor_wrapper<const_iterator> const_accessor;
++ typedef accessor_wrapper<iterator> accessor;
++
++};
++
++#else
++
++// Compare HashString pointers by dereferencing them.
++struct hashstring_ptr_less : public std::binary_function<const HashString*,
const HashString*, bool> {
++ size_t operator () (const HashString* one, const HashString* two) const
++ { return *one < *two; }
++};
++
++class DhtNodeList : public std::map<const HashString*, DhtNode*,
hashstring_ptr_less> {
++public:
++ typedef std::map<const HashString*, DhtNode*, hashstring_ptr_less>
base_type;
++
++ // Define accessor iterator with more convenient access to the key and
++ // element values. Allows changing the map definition more easily if
needed.
++ template<typename T>
++ struct accessor_wrapper : public T {
++ accessor_wrapper(const T& itr) : T(itr) { }
++
++ const HashString& id() const { return *(**this).first; }
++ DhtNode* node() const { return (**this).second; }
++ };
++
++ typedef accessor_wrapper<const_iterator> const_accessor;
++ typedef accessor_wrapper<iterator> accessor;
++
++ DhtNode* insert(DhtNode* n);
++
++};
++
++class DhtTrackerList : public std::map<HashString, DhtTracker*> {
++public:
++ typedef std::map<HashString, DhtTracker*> base_type;
++
++ template<typename T>
++ struct accessor_wrapper : public T {
++ accessor_wrapper(const T& itr) : T(itr) { }
++
++ const HashString& id() const { return (**this).first; }
++ DhtTracker* tracker() const { return (**this).second; }
++ };
++
++ typedef accessor_wrapper<const_iterator> const_accessor;
++ typedef accessor_wrapper<iterator> accessor;
++
++};
++#endif // HAVE_TR1
++
++inline
++DhtNode* DhtNodeList::insert(DhtNode* n) {
++ base_type::insert(std::make_pair<const HashString*, DhtNode*>(n, n));
++ return n;
++}
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_transaction.cc
+===================================================================
+--- libtorrent/src/dht/dht_transaction.cc (revision 0)
++++ libtorrent/src/dht/dht_transaction.cc (revision 0)
+@@ -0,0 +1,287 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include "torrent/exceptions.h"
++#include "torrent/object_stream.h"
++#include "tracker/tracker_dht.h"
++
++#include "dht_bucket.h"
++#include "dht_transaction.h"
++
++namespace torrent {
++
++DhtSearch::DhtSearch(const HashString& target, const DhtBucket& contacts)
++ : base_type(dht_compare_closer(target)),
++ m_pending(0),
++ m_contacted(0),
++ m_replied(0),
++ m_concurrency(3),
++ m_restart(false),
++ m_started(false),
++ m_next(end()) {
++
++ add_contacts(contacts);
++}
++
++DhtSearch::~DhtSearch() {
++ // Make sure transactions were destructed first. Since it is the
destruction
++ // of a transaction that triggers this destructor, that should always be
the
++ // case.
++ if (m_pending)
++ throw internal_error("DhtSearch::~DhtSearch called with pending
transactions.");
++
++ if (m_concurrency != 3)
++ throw internal_error("DhtSearch::~DhtSearch with invalid concurrency
limit.");
++
++ for (accessor itr = begin(); itr != end(); ++itr)
++ delete itr.node();
++}
++
++bool
++DhtSearch::add_contact(const HashString& id, const rak::socket_address* sa)
{
++ DhtNode* n = new DhtNode(id, sa);
++ bool added = insert(std::make_pair(n, this)).second;
++
++ if (!added)
++ delete n;
++ else
++ m_restart = true;
++
++ return added;
++}
++
++void
++DhtSearch::add_contacts(const DhtBucket& contacts) {
++ DhtBucketChain chain(&contacts);
++
++ // Add max_contacts=18 closest nodes, and fill up so we also have at
least 8 good nodes.
++ int needClosest = max_contacts - size();
++ int needGood = DhtBucket::num_nodes;
++
++ for (DhtBucket::const_iterator itr = chain.bucket()->begin(); needClosest
> 0 || needGood > 0; ++itr) {
++ while (itr == chain.bucket()->end()) {
++ if (!chain.next())
++ return;
++
++ itr = chain.bucket()->begin();
++ }
++
++ if ((!(*itr)->is_bad() || needClosest > 0) && add_contact((*itr)->id(),
(*itr)->address())) {
++ needGood -= !(*itr)->is_bad();
++ needClosest--;
++ }
++ }
++}
++
++// Check if a node has been contacted yet. This is the case if it is not
currently
++// being contacted, nor has it been found to be good or bad.
++bool
++DhtSearch::node_uncontacted(const DhtNode* node) const {
++ return !node->is_active() && !node->is_good() && !node->is_bad();
++}
++
++// After more contacts have been added, discard least closest nodes
++// except if node has a transaction pending.
++void
++DhtSearch::trim(bool final) {
++
++ // We keep:
++ // - the max_contacts=18 closest good or unknown nodes and all nodes
closer
++ // than them (to see if further searches find closer ones)
++ // - for announces, also the 8 closest good nodes (i.e. nodes that have
++ // replied) to have at least that many for the actual announce
++ // - any node that currently has transactions pending
++ //
++ // However, after exhausting all search nodes, we only keep good nodes.
++ //
++ // For our purposes, the node status is as follows:
++ // node is bad (contacted but hasn't replied) if is_bad()
++ // node is good (contacted and replied) if is_good()
++ // node is currently being contacted if is_active()
++ // node is new and unknown otherwise
++
++ int needClosest = final ? 0 : max_contacts;
++ int needGood = is_announce() ? DhtBucket::num_nodes : 0;
++
++ // We're done if we can't find any more nodes to contact.
++ m_next = end();
++
++ accessor itr = base_type::begin();
++ while (itr != end()) {
++ // If we have all we need, delete current node unless it is
++ // currently being contacted.
++ if (!itr.node()->is_active() && needClosest <= 0 &&
(!itr.node()->is_good() || needGood <= 0)) {
++ delete itr.node();
++ erase(itr++);
++ continue;
++ }
++
++ // Otherwise adjust needed counts appropriately.
++ needClosest--;
++ needGood -= itr.node()->is_good();
++
++ // Remember the first uncontacted node as the closest one to contact
next.
++ if (m_next == end() && node_uncontacted(itr.node()))
++ m_next = const_accessor(itr);
++
++ ++itr;
++ }
++
++ m_restart = false;
++}
++
++DhtSearch::const_accessor
++DhtSearch::get_contact() {
++ if (m_pending >= m_concurrency)
++ return end();
++
++ if (m_restart)
++ trim(false);
++
++ const_accessor ret = m_next;
++ if (ret == end())
++ return ret;
++
++ set_node_active(ret, true);
++ m_pending++;
++ m_contacted++;
++
++ // Find next node to contact: any node we haven't contacted yet.
++ while (++m_next != end()) {
++ if (node_uncontacted(m_next.node()))
++ break;
++ }
++
++ return ret;
++}
++
++void
++DhtSearch::node_status(const_accessor& n, bool success) {
++ if (n == end() || !n.node()->is_active())
++ throw internal_error("DhtSearch::node_status called for
invalid/inactive node.");
++
++ if (success) {
++ n.node()->set_good();
++ m_replied++;
++
++ } else {
++ n.node()->set_bad();
++ }
++
++ m_pending--;
++ set_node_active(n, false);
++}
++
++DhtSearch::const_accessor
++DhtAnnounce::start_announce() {
++ trim(true);
++
++ if (empty())
++ return end();
++
++ if (!complete() || m_next != end() || size() > DhtBucket::num_nodes)
++ throw internal_error("DhtSearch::start_announce called in inconsistent
state.");
++
++ m_contacted = m_pending = size();
++ m_replied = 0;
++
++ for (const_accessor itr(begin()); itr != end(); ++itr)
++ set_node_active(itr, true);
++
++ return const_accessor(begin());
++}
++
++void
++DhtTransactionPacket::build_buffer(const Object& data) {
++ char buffer[1500]; // If the message would exceed an Ethernet frame,
something went very wrong.
++ object_buffer_t result = object_write_bencode_c(object_write_to_buffer,
NULL, std::make_pair(buffer, buffer + sizeof(buffer)), &data);
++
++ m_length = result.second - buffer;
++ m_data = new char[m_length];
++ memcpy(m_data, buffer, m_length);
++}
++
++DhtTransaction::DhtTransaction(int quick_timeout, int timeout, const
HashString& id, const rak::socket_address* sa)
++ : m_hasQuickTimeout(quick_timeout > 0),
++ m_id(id),
++ m_sa(*sa),
++ m_timeout(cachedTime.seconds() + timeout),
++ m_quickTimeout(cachedTime.seconds() + quick_timeout),
++ m_retry(3),
++ m_packet(NULL) {
++
++}
++
++DhtTransaction::~DhtTransaction() {
++ if (m_packet != NULL)
++ m_packet->set_failed();
++}
++
++void
++DhtTransactionSearch::set_stalled() {
++ if (!m_hasQuickTimeout)
++ throw internal_error("DhtTransactionSearch::set_stalled called on
already stalled transaction.");
++
++ m_hasQuickTimeout = false;
++ m_search->m_concurrency++;
++}
++
++void
++DhtTransactionSearch::complete(bool success) {
++ if (m_node == m_search->end())
++ throw internal_error("DhtTransactionSearch::complete called multiple
times.");
++
++ if (m_node.search() != m_search)
++ throw internal_error("DhtTransactionSearch::complete called for node
from wrong search.");
++
++ if (!m_hasQuickTimeout)
++ m_search->m_concurrency--;
++
++ m_search->node_status(m_node, success);
++ m_node = m_search->end();
++}
++
++DhtTransactionSearch::~DhtTransactionSearch() {
++ if (m_node != m_search->end())
++ complete(false);
++
++ if (m_search->complete())
++ delete m_search;
++}
++
++}
+Index: libtorrent/src/dht/dht_node.cc
+===================================================================
+--- libtorrent/src/dht/dht_node.cc (revision 0)
++++ libtorrent/src/dht/dht_node.cc (revision 0)
+@@ -0,0 +1,89 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++#include "globals.h"
++
++#include "torrent/exceptions.h"
++#include "torrent/object.h"
++
++#include "dht_node.h"
++
++namespace torrent {
++
++DhtNode::DhtNode(const HashString& id, const rak::socket_address* sa) :
++ HashString(id),
++ m_socketAddress(*sa),
++ m_lastSeen(0),
++ m_recentlyActive(false),
++ m_recentlyInactive(0),
++ m_bucket(NULL) {
++
++ if (sa->family() != rak::socket_address::af_inet)
++ throw resource_error("Address not af_inet");
++}
++
++DhtNode::DhtNode(const std::string& id, const Object& cache) :
++ HashString(*HashString::cast_from(id.c_str())),
++ m_recentlyActive(false),
++ m_recentlyInactive(0),
++ m_bucket(NULL) {
++
++ rak::socket_address_inet* sa = m_socketAddress.sa_inet();
++ sa->set_family();
++ sa->set_address_h(cache.get_key_value("i"));
++ sa->set_port(cache.get_key_value("p"));
++ m_lastSeen = cache.get_key_value("t");
++ update();
++}
++
++char*
++DhtNode::store_compact(char* buffer) const {
++ HashString::cast_from(buffer)->assign(data());
++ *(uint32_t*) (buffer+20) = address()->sa_inet()->address_n();
++ *(uint16_t*) (buffer+24) = address()->sa_inet()->port_n();
++ return buffer+26;
++}
++
++Object*
++DhtNode::store_cache(Object* container) const {
++ container->insert_key("i", m_socketAddress.sa_inet()->address_h());
++ container->insert_key("p", m_socketAddress.sa_inet()->port());
++ container->insert_key("t", m_lastSeen);
++ return container;
++}
++
++}
+Index: libtorrent/src/dht/dht_server.cc
+===================================================================
+--- libtorrent/src/dht/dht_server.cc (revision 0)
++++ libtorrent/src/dht/dht_server.cc (revision 0)
+@@ -0,0 +1,912 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++#include "globals.h"
++
++#include <algorithm>
++#include <sstream>
++#include <rak/error_number.h>
++#include <rak/string_manip.h>
++
++#include "net/throttle_manager.h"
++#include "torrent/exceptions.h"
++#include "torrent/connection_manager.h"
++#include "torrent/object.h"
++#include "torrent/object_stream.h"
++#include "torrent/poll.h"
++#include "tracker/tracker_dht.h"
++
++#include "dht_bucket.h"
++#include "dht_router.h"
++#include "dht_transaction.h"
++
++#include "manager.h"
++
++namespace torrent {
++
++const char* DhtServer::queries[] = {
++ "ping",
++ "find_node",
++ "get_peers",
++ "announce_peer",
++};
++
++// Error in DHT protocol, avoids std::string ctor from communication_error
++class dht_error : public network_error {
++public:
++ dht_error(int code, const char* message) : m_message(message),
m_code(code) {}
++
++ virtual int code() const throw() { return m_code; }
++ virtual const char* what() const throw() { return m_message; }
++
++private:
++ const char* m_message;
++ int m_code;
++};
++
++DhtServer::DhtServer(DhtRouter* router) :
++ m_router(router),
++
++ m_uploadThrottle(60),
++ m_downloadThrottle(60),
++
++ m_networkUp(false) {
++
++ get_fd().clear();
++ reset_statistics();
++
++ // Reserve a socket for the DHT server, even though we don't
++ // actually open it until the server is started, which may not
++ // happen until the first non-private torrent is started.
++ manager->connection_manager()->inc_socket_count();
++}
++
++DhtServer::~DhtServer() {
++ stop();
++
++ std::for_each(m_highQueue.begin(), m_highQueue.end(),
rak::call_delete<DhtTransactionPacket>());
++ std::for_each(m_lowQueue.begin(), m_lowQueue.end(),
rak::call_delete<DhtTransactionPacket>());
++
++ manager->connection_manager()->dec_socket_count();
++}
++
++void
++DhtServer::start(int port) {
++ try {
++ if (!get_fd().open_datagram() || !get_fd().set_nonblock())
++ throw resource_error("Could not allocate datagram socket.");
++
++ if (!get_fd().set_reuse_address(true))
++ throw resource_error("Could not set listening port to reuse
address.");
++
++ rak::socket_address sa = *m_router->address();
++ sa.set_port(port);
++
++ if (!get_fd().bind(sa))
++ throw resource_error("Could not bind datagram socket.");
++
++ } catch (...) {
++ get_fd().close();
++ get_fd().clear();
++ throw;
++ }
++
++ m_taskTimeout.set_slot(rak::mem_fn(this, &DhtServer::receive_timeout));
++
++
m_uploadThrottle.set_list_iterator(manager->upload_throttle()->throttle_list()->end());
++
m_uploadThrottle.slot_activate(rak::make_mem_fun(reinterpret_cast<SocketBase*>(this),
&SocketBase::receive_throttle_up_activate));
++
++
m_downloadThrottle.set_list_iterator(manager->download_throttle()->throttle_list()->end());
++
manager->download_throttle()->throttle_list()->insert(&m_downloadThrottle);
++
++ manager->poll()->open(this);
++ manager->poll()->insert_read(this);
++ manager->poll()->insert_error(this);
++}
++
++void
++DhtServer::stop() {
++ if (!is_active())
++ return;
++
++ clear_transactions();
++
++ priority_queue_erase(&taskScheduler, &m_taskTimeout);
++
++ manager->upload_throttle()->throttle_list()->erase(&m_uploadThrottle);
++ manager->download_throttle()->throttle_list()->erase(&m_downloadThrottle);
++
++ manager->poll()->remove_read(this);
++ manager->poll()->remove_write(this);
++ manager->poll()->remove_error(this);
++ manager->poll()->close(this);
++
++ get_fd().close();
++ get_fd().clear();
++
++ m_networkUp = false;
++}
++
++void
++DhtServer::reset_statistics() {
++ m_queriesReceived = 0;
++ m_queriesSent = 0;
++ m_repliesReceived = 0;
++
++ m_uploadThrottle.rate()->set_total(0);
++ m_downloadThrottle.rate()->set_total(0);
++}
++
++// Ping a node whose ID we know.
++void
++DhtServer::ping(const HashString& id, const rak::socket_address* sa) {
++ // No point pinging a node that we're already contacting otherwise.
++ transaction_itr itr = m_transactions.lower_bound(DhtTransaction::key(sa,
0));
++ if (itr == m_transactions.end() || (itr->first >> 32 !=
DhtTransaction::key(sa, 0) >> 32))
++ add_transaction(new DhtTransactionPing(id, sa), packet_prio_low);
++}
++
++// Contact nodes in given bucket and ask for their nodes closest to target.
++void
++DhtServer::find_node(const DhtBucket& contacts, const HashString& target) {
++ DhtSearch* search = new DhtSearch(target, contacts);
++
++ DhtSearch::const_accessor n;
++ while ((n = search->get_contact()) != search->end())
++ add_transaction(new DhtTransactionFindNode(n), packet_prio_low);
++
++ // This shouldn't happen, it means we had no contactable nodes at all.
++ if (!search->start()) {
++ delete search;
++ return;
++ }
++
++}
++
++void
++DhtServer::announce(const DhtBucket& contacts, const HashString& infoHash,
TrackerDht* tracker) {
++ DhtAnnounce* announce = new DhtAnnounce(infoHash, tracker, contacts);
++
++ DhtSearch::const_accessor n;
++ while ((n = announce->get_contact()) != announce->end())
++ add_transaction(new DhtTransactionFindNode(n), packet_prio_high);
++
++ // This can only happen if all nodes we know are bad.
++ if (!announce->start()) {
++ tracker->receive_failed("No DHT nodes available for peer search.");
++ delete announce;
++ return;
++ }
++
++ tracker->receive_status(announce->num_replied(),
announce->num_contacted(), "Searching");
++}
++
++void
++DhtServer::cancel_announce(DownloadInfo* info, const TrackerDht* tracker) {
++ transaction_itr itr = m_transactions.begin();
++
++ while (itr != m_transactions.end()) {
++ if (itr->second->is_search() &&
itr->second->as_search()->search()->is_announce()) {
++ DhtAnnounce* announce =
reinterpret_cast<DhtAnnounce*>(itr->second->as_search()->search());
++
++ if ((!info || announce->target() == info->hash()) && (!tracker ||
announce->tracker() == tracker)) {
++ itr = cancel_transaction(itr);
++ continue; // itr has been incremented already
++ }
++ }
++
++ ++itr;
++ }
++}
++
++void
++DhtServer::update() {
++ // Reset this every 15 minutes. It'll get set back to true if we receive
++ // any valid packets. This allows detecting when the entire network goes
++ // down, and prevents all nodes from getting removed as unresponsive.
++ m_networkUp = false;
++}
++
++void
++DhtServer::process_query(const Object& transactionId, const HashString& id,
const rak::socket_address* sa, Object& request) {
++ m_queriesReceived++;
++ m_networkUp = true;
++
++ std::string& query = request.get_key_string("q");
++
++ Object& arg = request.get_key("a");
++
++ // Construct reply.
++ Object reply(Object::TYPE_MAP);
++
++ if (query == "find_node")
++ create_find_node_response(arg, reply);
++
++ else if (query == "get_peers")
++ create_get_peers_response(arg, sa, reply);
++
++ else if (query == "announce_peer")
++ create_announce_peer_response(arg, sa, reply);
++
++ else if (query != "ping")
++ throw dht_error(dht_error_bad_method, "Unknown query type.");
++
++ m_router->node_queried(id, sa);
++
++ create_response(transactionId, sa, reply);
++}
++
++void
++DhtServer::create_find_node_response(const Object& arg, Object& reply) {
++ const std::string& target = arg.get_key_string("target");
++
++ if (target.length() < HashString::size_data)
++ throw dht_error(dht_error_protocol, "target string too short");
++
++ char compact[26*8];
++ char* end = m_router->store_closest_nodes(*HashString::cast_from(target),
compact, compact + 26*8);
++
++ if (end == compact)
++ throw dht_error(dht_error_generic, "No nodes");
++
++ reply.insert_key("nodes", std::string(compact, end));
++}
++
++void
++DhtServer::create_get_peers_response(const Object& arg, const
rak::socket_address* sa, Object& reply) {
++ reply.insert_key("token", m_router->make_token(sa));
++
++ const std::string& info_hash_str = arg.get_key_string("info_hash");
++
++ if (info_hash_str.length() < HashString::size_data)
++ throw dht_error(dht_error_protocol, "info hash too short");
++
++ const HashString* info_hash = HashString::cast_from(info_hash_str);
++
++ DhtTracker* tracker = m_router->get_tracker(*info_hash, false);
++
++ // If we're not tracking or have no peers, send closest nodes.
++ if (!tracker || tracker->empty()) {
++ char compact[26*8];
++ char* end = m_router->store_closest_nodes(*info_hash, compact, compact
+ 26*8);
++
++ if (end == compact)
++ throw dht_error(dht_error_generic, "No peers nor nodes");
++
++ reply.insert_key("nodes", std::string(compact, end));
++
++ } else {
++ Object& values = reply.insert_key("values", Object(Object::TYPE_LIST));
++ values.insert_back(tracker->get_peers());
++ }
++}
++
++void
++DhtServer::create_announce_peer_response(const Object& arg, const
rak::socket_address* sa, Object& reply) {
++ const std::string& info_hash_str = arg.get_key_string("info_hash");
++
++ if (info_hash_str.length() < HashString::size_data)
++ throw dht_error(dht_error_protocol, "info hash too short");
++
++ const HashString* info_hash = HashString::cast_from(info_hash_str);
++
++ if (!m_router->token_valid(arg.get_key_string("token"), sa))
++ throw dht_error(dht_error_protocol, "Token invalid.");
++
++ DhtTracker* tracker = m_router->get_tracker(*info_hash, true);
++ rak::socket_address peer = *sa;
++ peer.set_port(arg.get_key_value("port"));
++ tracker->add_peer(&peer);
++}
++
++void
++DhtServer::process_response(int transactionId, const HashString& id, const
rak::socket_address* sa, Object& request) {
++ transaction_itr itr = m_transactions.find(DhtTransaction::key(sa,
transactionId));
++
++ // Response to a transaction we don't have in our table. At this point
it's
++ // impossible to tell whether it used to be a valid transaction but timed
out
++ // or the node did not return the ID we sent it. Best we can do is ignore
the
++ // reply.
++ if (itr == m_transactions.end())
++ return;
++
++ m_repliesReceived++;
++ m_networkUp = true;
++
++ const Object& response = request.get_key("r");
++
++ // If we contact a node but the reply ID or key (address) don't match,
ignore the reply
++ // to prevent interference from rogue nodes.
++ // (But ignore the port for comparison purposes since it may be changed
by NAT.)
++ DhtTransaction* transaction = itr->second;
++ if (DhtTransaction::key(sa, transactionId) !=
transaction->key(transactionId) || (id != transaction->id() &&
transaction->id() != *m_router && transaction->id() != m_router->zero_id))
++ return;
++
++ switch (transaction->type()) {
++ case DhtTransaction::DHT_FIND_NODE:
++ parse_find_node_reply(transaction->as_find_node(),
response.get_key_string("nodes"));
++ break;
++
++ case DhtTransaction::DHT_GET_PEERS:
++ parse_get_peers_reply(transaction->as_get_peers(), response);
++ break;
++
++ // Nothing to do for DHT_PING and DHT_ANNOUNCE_PEER
++ default:
++ break;
++ }
++
++ // Mark node responsive only if all processing was successful, without
errors.
++ m_router->node_replied(id, sa);
++
++ delete itr->second;
++ m_transactions.erase(itr);
++}
++
++void
++DhtServer::process_error(int transactionId, const rak::socket_address* sa,
Object& request) {
++ transaction_itr itr = m_transactions.find(DhtTransaction::key(sa,
transactionId));
++
++ if (itr == m_transactions.end())
++ return;
++
++ m_repliesReceived++;
++ m_networkUp = true;
++
++ // Don't mark node as good (because it replied) or bad (because it
returned an error).
++ // If it consistently returns errors for valid queries it's probably
broken. But a
++ // few error messages are acceptable. So we do nothing and pretend the
query never happened.
++
++ cancel_transaction(itr);
++}
++
++void
++DhtServer::parse_node_info(node_info_list& l, const std::string& n) {
++ if (sizeof(const compact_node_info) != 26)
++ throw internal_error("DhtServer::parse_node_info(...) bad struct
size.");
++
++ std::copy(reinterpret_cast<const compact_node_info*>(n.c_str()),
++ reinterpret_cast<const compact_node_info*>(n.c_str() + n.size()
- n.size() % sizeof(compact_node_info)),
++ std::back_inserter(l));
++}
++
++void
++DhtServer::parse_find_node_reply(DhtTransactionSearch* transaction, const
std::string& nodes) {
++ DhtSearch* search = transaction->search();
++
++ transaction->complete(true);
++
++ node_info_list l;
++ parse_node_info(l, nodes);
++
++ for (node_info_list::iterator itr = l.begin(); itr != l.end(); ++itr) {
++ if (itr->id() != m_router->id()) {
++ rak::socket_address sa = itr->address();
++ search->add_contact(itr->id(), &sa);
++ }
++ }
++
++ find_node_next(transaction);
++}
++
++void
++DhtServer::parse_get_peers_reply(DhtTransactionGetPeers* transaction, const
Object& response) {
++ DhtAnnounce* announce = transaction->announce();
++
++ transaction->complete(true);
++
++ if (response.has_key_list("values"))
++
announce->tracker()->receive_peers((*response.get_key_list("values").begin()).as_string());
++
++ if (response.has_key_string("token"))
++ add_transaction(new DhtTransactionAnnounce(transaction->id(),
transaction->address(), announce->target(),
response.get_key_string("token")), packet_prio_low);
++
++ announce->tracker()->receive_status(announce->num_replied(),
announce->num_contacted(), "Announcing");
++
++ if (!announce->complete())
++ return;
++
++ DhtTracker* torrent =
m_router->get_tracker(transaction->announce()->target(), false);
++ if (torrent != NULL)
++ announce->tracker()->receive_peers(torrent->get_peers());
++
++ announce->tracker()->receive_success();
++}
++
++void
++DhtServer::find_node_next(DhtTransactionSearch* transaction) {
++ DhtSearch::const_accessor node;
++ while ((node = transaction->search()->get_contact()) !=
transaction->search()->end()) {
++ int prio = packet_prio_low;
++ if (transaction->search()->is_announce())
++ prio = packet_prio_high;
++
++ add_transaction(new DhtTransactionFindNode(node), prio);
++ }
++
++ if (transaction->search()->is_announce())
++
reinterpret_cast<DhtAnnounce*>(transaction->search())->tracker()->receive_status(transaction->search()->num_replied(),
transaction->search()->num_contacted(), "Searching");
++
++ if (!transaction->search()->complete())
++ return;
++
++ if (!transaction->search()->is_announce())
++ return;
++
++ // We have found the 8 closest nodes to the info hash. Retrieve peers
++ // from them and announce to them.
++ DhtAnnounce* announce = transaction->as_get_peers()->announce();
++
++ for (node = announce->start_announce(); node != announce->end(); ++node)
++ add_transaction(new DhtTransactionGetPeers(node), packet_prio_high);
++
++ if (announce->complete())
++ return announce->tracker()->receive_failed("DHT search unsuccessful.");
++
++ announce->tracker()->receive_status(announce->num_replied(),
announce->num_contacted(), "Announcing");
++}
++
++void
++DhtServer::add_packet(DhtTransactionPacket* packet, int priority) {
++ switch (priority) {
++ // High priority packets are for important queries, and quite small.
++ // They're added to front of high priority queue and thus will be the
++ // next packets sent.
++ case packet_prio_high:
++ m_highQueue.push_front(packet);
++ break;
++
++ // Low priority query packets are added to the back of the high priority
++ // queue and will be sent when all high priority packets have been
transmitted.
++ case packet_prio_low:
++ m_highQueue.push_back(packet);
++ break;
++
++ // Reply packets will be processed after all of our own packets have
been send.
++ case packet_prio_reply:
++ m_lowQueue.push_back(packet);
++ break;
++
++ default:
++ throw internal_error("DhtServer::add_packet called with invalid
priority.");
++ }
++}
++
++void
++DhtServer::create_query(transaction_itr itr, int tID, const
rak::socket_address* sa, int priority) {
++ if (itr->second->id() == m_router->id()) {
++ delete itr->second;
++ m_transactions.erase(itr);
++ return; // Don't send to ourself.
++ }
++
++ Object query(Object::TYPE_MAP);
++
++ Object q(Object::TYPE_MAP);
++ q.insert_key("id", m_router->str());
++
++ DhtTransaction* transaction = itr->second;
++ switch (transaction->type()) {
++ case DhtTransaction::DHT_PING:
++ // nothing to do
++ break;
++
++ case DhtTransaction::DHT_FIND_NODE:
++ q.insert_key("target",
transaction->as_find_node()->search()->target().str());
++ break;
++
++ case DhtTransaction::DHT_GET_PEERS:
++ q.insert_key("info_hash",
transaction->as_get_peers()->announce()->target().str());
++ break;
++
++ case DhtTransaction::DHT_ANNOUNCE_PEER:
++ q.insert_key("info_hash",
transaction->as_announce()->info_hash().str());
++ q.insert_key("port", manager->connection_manager()->listen_port());
++ q.insert_key("token", transaction->as_announce()->token());
++ break;
++ }
++
++ char trans_id = tID;
++ query.insert_key("t", std::string(&trans_id, 1));
++ query.insert_key("y", "q");
++ query.insert_key("q", queries[transaction->type()]);
++ query.insert_key("a", q);
++ query.insert_key("v", PEER_VERSION);
++
++ DhtTransactionPacket* packet = new
DhtTransactionPacket(transaction->address(), query, tID, transaction);
++ transaction->set_packet(packet);
++ add_packet(packet, priority);
++
++ m_queriesSent++;
++}
++
++void
++DhtServer::create_response(const Object& transactionId, const
rak::socket_address* sa, Object& r) {
++ Object reply(Object::TYPE_MAP);
++ r.insert_key("id", m_router->str());
++
++ reply.insert_key("t", transactionId);
++ reply.insert_key("y", "r");
++ reply.insert_key("r", r);
++ reply.insert_key("v", PEER_VERSION);
++
++ add_packet(new DhtTransactionPacket(sa, reply), packet_prio_reply);
++}
++
++void
++DhtServer::create_error(const Object& transactionId, const
rak::socket_address* sa, int num, const std::string& msg) {
++ Object error(Object::TYPE_MAP);
++
++ error.insert_key("t", transactionId);
++ error.insert_key("y", "e");
++ error.insert_key("v", PEER_VERSION);
++
++ Object& e = error.insert_key("e", Object(Object::TYPE_LIST));
++ e.insert_back(num);
++ e.insert_back(msg);
++
++ add_packet(new DhtTransactionPacket(sa, error), packet_prio_reply);
++}
++
++int
++DhtServer::add_transaction(DhtTransaction* transaction, int priority) {
++ // Try random transaction ID. This is to make it less likely that we reuse
++ // a transaction ID from an earlier transaction which timed out and we
forgot
++ // about it, so that if the node replies after the timeout it's less
likely
++ // that we match the reply to the wrong transaction.
++ //
++ // If there's an existing transaction with the random ID we search for
the next
++ // unused one. Since normally only one or two transactions will be active
per
++ // node, a collision is extremely unlikely, and a linear search for the
first
++ // open one is the most efficient.
++ unsigned int rnd = (uint8_t)random();
++ unsigned int id = rnd;
++
++ transaction_itr insertItr =
m_transactions.lower_bound(transaction->key(rnd));
++
++ // If key matches, keep trying successive IDs.
++ while (insertItr != m_transactions.end() && insertItr->first ==
transaction->key(id)) {
++ ++insertItr;
++ id = (uint8_t)(id + 1);
++
++ // Give up after trying all possible IDs. This should never happen.
++ if (id == rnd) {
++ delete transaction;
++ return -1;
++ }
++
++ // Transaction ID wrapped around, reset iterator.
++ if (id == 0)
++ insertItr = m_transactions.lower_bound(transaction->key(id));
++ }
++
++ // We know where to insert it, so pass that as hint.
++ insertItr = m_transactions.insert(insertItr,
std::make_pair(transaction->key(id), transaction));
++
++ create_query(insertItr, id, transaction->address(), priority);
++
++ start_write();
++
++ return id;
++}
++
++// Transaction received no reply and timed out. Mark node as bad and remove
++// transaction (except if it was only the quick timeout).
++DhtServer::transaction_itr
++DhtServer::failed_transaction(transaction_itr itr, bool quick) {
++ DhtTransaction* transaction = itr->second;
++
++ // If it was a known node, remember that it didn't reply, unless the
transaction
++ // is only stalled (had quick timeout, but not full timeout). Also if the
++ // transaction still has an associated packet, the packet never got sent
due to
++ // throttling, so don't blame the remote node for not replying.
++ // Finally, if we haven't received anything whatsoever so far, assume the
entire
++ // network is down and so we can't blame the node either.
++ if (!quick && m_networkUp && transaction->packet() == NULL &&
transaction->id() != *m_router)
++ m_router->node_inactive(transaction->id(), transaction->address());
++
++ switch (transaction->type()) {
++ case DhtTransaction::DHT_FIND_NODE:
++ if (quick)
++ transaction->as_find_node()->set_stalled();
++ else
++ transaction->as_find_node()->complete(false);
++
++ find_node_next(transaction->as_find_node());
++ break;
++
++ case DhtTransaction::DHT_GET_PEERS:
++ DhtAnnounce* announce;
++ announce = transaction->as_get_peers()->announce();
++ transaction->as_get_peers()->complete(false);
++ if (announce->complete())
++ announce->tracker()->receive_success();
++ break;
++
++ // Nothing to for DHT_PING and DHT_ANNOUNCE_PEER
++ default:
++ break;
++ }
++
++ if (quick) {
++ return ++itr; // don't actually delete the transaction until
the final timeout
++
++ } else {
++ delete itr->second;
++ m_transactions.erase(itr++);
++ return itr;
++ }
++}
++
++// Cancel transaction before the timeout (i.e. we aren't sure if the node
is bad yet).
++DhtServer::transaction_itr
++DhtServer::cancel_transaction(transaction_itr itr) {
++ DhtTransaction* transaction = itr->second;
++
++ // If this was the last transaction of an announce search, notify tracker.
++ if (transaction->is_search() &&
transaction->as_search()->search()->is_announce()) {
++ transaction->as_search()->complete(false);
++
++ DhtAnnounce* announce =
reinterpret_cast<DhtAnnounce*>(transaction->as_search()->search());
++ if (announce->complete()) {
++ // If we got no peers, it's a failure if the announces all failed as
well.
++ // We don't want no peers to be a failure in general since it may
just be
++ // a dead torrent.
++ if (announce->num_replied() == 0 && !announce->tracker()->has_peers())
++ announce->tracker()->receive_failed("Announce failed");
++ else
++ announce->tracker()->receive_success();
++ }
++ }
++
++ delete itr->second;
++ m_transactions.erase(itr++);
++ return itr;
++}
++
++void
++DhtServer::clear_transactions() {
++ std::for_each(m_transactions.begin(), m_transactions.end(),
++ rak::on(rak::mem_ref(&transaction_map::value_type::second),
++ rak::call_delete<DhtTransaction>()));
++ m_transactions.clear();
++}
++
++void
++DhtServer::event_read() {
++ uint32_t total = 0;
++ std::istringstream sstream;
++
++ sstream.imbue(std::locale::classic());
++
++ while (true) {
++ rak::socket_address sa;
++ int type = '?';
++ const Object* transactionId = NULL;
++ const HashString* nodeId = NULL;
++
++ try {
++ char buffer[2048];
++ int32_t read = read_datagram(buffer, sizeof(buffer), &sa);
++
++ if (read < 0)
++ break;
++
++ total += read;
++ sstream.str(std::string(buffer, read));
++
++ Object request;
++ sstream >> request;
++
++ // Could throw dht_error with more descriptive error messages but
++ // we can't return an error unless the packet is valid and we have the
++ // transaction ID anyway. Searching the routing table for the source
++ // address to find the node is too expensive, so just ignore the
packet.
++ if (sstream.fail() || !request.is_map() || !request.has_key("t") ||
!request.has_key_string("y"))
++ continue;
++
++ // Read the items required for proper error handling first.
++ transactionId = &request.get_key("t");
++ if (request.get_key_string("y").length() == 1)
++ type = request.get_key_string("y")[0];
++
++ // Queries and replies have node ID in different dictionaries.
++ if (type == 'r' || type == 'q') {
++ const std::string& nodeIdStr = request.get_key(type == 'q' ? "a" :
"r").get_key_string("id");
++
++ if (nodeIdStr.length() < HashString::size_data)
++ throw dht_error(dht_error_protocol, "id value too short");
++
++ nodeId = HashString::cast_from(nodeIdStr);
++ }
++
++ // Sanity check the returned transaction ID.
++ if ((type == 'r' || type == 'e') &&
++ (!transactionId->is_string() ||
transactionId->as_string().length() != 1))
++ throw dht_error(dht_error_protocol, "Invalid transaction ID
type/length.");
++
++ switch (type) {
++ case 'q':
++ process_query(*transactionId, *nodeId, &sa, request);
++ break;
++
++ case 'r':
++ process_response(((unsigned
char*)transactionId->as_string().c_str())[0], *nodeId, &sa, request);
++ break;
++
++ case 'e':
++ process_error(((unsigned
char*)transactionId->as_string().c_str())[0], &sa, request);
++ break;
++
++ default:
++ throw dht_error(dht_error_bad_method, "Unknown message type.");
++ }
++
++ } catch (bencode_error& e) {
++ if (type == 'q' && transactionId != NULL)
++ create_error(*transactionId, &sa, dht_error_protocol,
std::string("Malformed packet: ") + e.what());
++ else if ((type == 'r' || type == 'e') && nodeId != NULL)
++ m_router->node_inactive(*nodeId, &sa);
++
++ } catch (dht_error& e) {
++ // If node was querying us, reply with error packet, otherwise mark
the node as "query failed",
++ // so that if it repeatedly sends malformed replies we will drop it
instead of propagating it
++ // to other nodes.
++ if (type == 'q' && transactionId != NULL)
++ create_error(*transactionId, &sa, e.code(), e.what());
++ else if ((type == 'r' || type == 'e') && nodeId != NULL)
++ m_router->node_inactive(*nodeId, &sa);
++
++ } catch (network_error& e) {
++
++ }
++ }
++
++
manager->download_throttle()->throttle_list()->node_used_unthrottled(total);
++ start_write();
++}
++
++bool
++DhtServer::process_queue(packet_queue& queue, uint32_t* quota) {
++ uint32_t used = 0;
++
++ while (!queue.empty()) {
++ DhtTransactionPacket* packet = queue.front();
++
++ // Make sure its transaction hasn't timed out yet, if it has/had one
++ // and don't bother sending non-transaction packets after more than
++ // 15 seconds in the queue.
++ if (packet->has_failed() || packet->age() > 15) {
++ delete packet;
++ queue.pop_front();
++ continue;
++ }
++
++ if (packet->length() > *quota) {
++
manager->upload_throttle()->throttle_list()->node_used(&m_uploadThrottle,
used);
++ return false;
++ }
++
++ queue.pop_front();
++
++ try {
++ int written = write_datagram(packet->c_str(), packet->length(),
packet->address());
++
++ if (written == -1)
++ throw network_error();
++
++ used += written;
++ *quota -= written;
++
++ if ((unsigned int)written != packet->length())
++ throw network_error();
++
++ } catch (network_error& e) {
++ // Couldn't write packet, maybe something wrong with node address or
routing, so mark node as bad.
++ if (packet->has_transaction()) {
++ transaction_itr itr =
m_transactions.find(packet->transaction()->key(packet->id()));
++ if (itr == m_transactions.end())
++ throw internal_error("DhtServer::process_queue could not find
transaction.");
++
++ failed_transaction(itr, false);
++ }
++ }
++
++ if (packet->has_transaction())
++ packet->transaction()->set_packet(NULL);
++
++ delete packet;
++ }
++
++ manager->upload_throttle()->throttle_list()->node_used(&m_uploadThrottle,
used);
++ return true;
++}
++
++void
++DhtServer::event_write() {
++ if (m_highQueue.empty() && m_lowQueue.empty())
++ throw internal_error("DhtServer::event_write called but both write
queues are empty.");
++
++ if
(!manager->upload_throttle()->throttle_list()->is_throttled(&m_uploadThrottle))
++ throw internal_error("DhtServer::event_write called while not in
throttle list.");
++
++ uint32_t quota =
manager->upload_throttle()->throttle_list()->node_quota(&m_uploadThrottle);
++
++ if (quota == 0 || !process_queue(m_highQueue, &quota) ||
!process_queue(m_lowQueue, &quota)) {
++ manager->poll()->remove_write(this);
++
manager->upload_throttle()->throttle_list()->node_deactivate(&m_uploadThrottle);
++
++ } else if (m_highQueue.empty() && m_lowQueue.empty()) {
++ manager->poll()->remove_write(this);
++ manager->upload_throttle()->throttle_list()->erase(&m_uploadThrottle);
++ }
++}
++
++void
++DhtServer::event_error() {
++}
++
++void
++DhtServer::start_write() {
++ if ((!m_highQueue.empty() || !m_lowQueue.empty()) &&
!manager->upload_throttle()->throttle_list()->is_throttled(&m_uploadThrottle))
{
++ manager->upload_throttle()->throttle_list()->insert(&m_uploadThrottle);
++ manager->poll()->insert_write(this);
++ }
++
++ if (!m_taskTimeout.is_queued() && !m_transactions.empty())
++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(5)).round_seconds());
++}
++
++void
++DhtServer::receive_timeout() {
++ transaction_itr itr = m_transactions.begin();
++ while (itr != m_transactions.end()) {
++ if (itr->second->has_quick_timeout() && itr->second->quick_timeout() <
cachedTime.seconds()) {
++ itr = failed_transaction(itr, true);
++
++ } else if (itr->second->timeout() < cachedTime.seconds()) {
++ itr = failed_transaction(itr, false);
++
++ } else {
++ ++itr;
++ }
++ }
++
++ start_write();
++}
++
++}
+Index: libtorrent/src/dht/Makefile.am
+===================================================================
+--- libtorrent/src/dht/Makefile.am (revision 0)
++++ libtorrent/src/dht/Makefile.am (revision 0)
+@@ -0,0 +1,19 @@
++noinst_LTLIBRARIES = libsub_dht.la
++
++libsub_dht_la_SOURCES = \
++ dht_bucket.cc \
++ dht_bucket.h \
++ dht_hash_map.h \
++ dht_node.cc \
++ dht_node.h \
++ dht_router.cc \
++ dht_router.h \
++ dht_server.cc \
++ dht_server.h \
++ dht_size_list.h \
++ dht_tracker.cc \
++ dht_tracker.h \
++ dht_transaction.cc \
++ dht_transaction.h
++
++INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir)
+Index: libtorrent/src/dht/dht_transaction.h
+===================================================================
+--- libtorrent/src/dht/dht_transaction.h (revision 0)
++++ libtorrent/src/dht/dht_transaction.h (revision 0)
+@@ -0,0 +1,363 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_TRANSACTION_H
++#define LIBTORRENT_DHT_TRANSACTION_H
++
++#include <map>
++
++#include <rak/socket_address.h>
++
++#include "dht/dht_node.h"
++#include "torrent/hash_string.h"
++
++namespace torrent {
++
++class DhtBucket;
++class TrackerDht;
++
++class DhtSearch;
++class DhtTransactionSearch;
++
++class DhtTransaction;
++class DhtTransactionPing;
++class DhtTransactionFindNode;
++class DhtTransactionGetPeers;
++class DhtTransactionAnnounce;
++
++// DhtSearch implements the DHT search algorithm and holds search data
++// that needs to be persistent across multiple find_node transactions.
++//
++// DhtAnnounce is a derived class used for searches that will eventually
++// lead to an announce to the closest nodes.
++
++
++// Compare predicate for ID closeness.
++struct dht_compare_closer : public std::binary_function<const DhtNode*,
const DhtNode*, bool> {
++ dht_compare_closer(const HashString& target) : m_target(target) { }
++
++ bool operator () (const DhtNode* one, const DhtNode* two) const;
++
++ const HashString& target() const { return m_target; }
++
++ HashString m_target;
++};
++
++// DhtSearch contains a list of nodes sorted by closeness to the given
target,
++// and returns what nodes to contact with up to three concurrent
transactions pending.
++// The map element is the DhtSearch object itself to allow the returned
accessors
++// to know which search a given node belongs to.
++class DhtSearch : protected std::map<DhtNode*, DhtSearch*,
dht_compare_closer> {
++ friend class DhtTransactionSearch;
++
++public:
++ typedef std::map<DhtNode*, DhtSearch*, dht_compare_closer> base_type;
++
++ // Number of closest potential contact nodes to keep.
++ static const unsigned int max_contacts = 18;
++
++ DhtSearch(const HashString& target, const DhtBucket& contacts);
++ virtual ~DhtSearch();
++
++ // Wrapper for iterators, allowing more convenient access to the key
++ // and element values, which also makes it easier to change the container
++ // without having to modify much code using iterators.
++ template <typename T>
++ struct accessor_wrapper : public T {
++ accessor_wrapper() { }
++ accessor_wrapper(const T& itr) : T(itr) { }
++
++ DhtNode* node() const { return
(**this).first; }
++ DhtSearch* search() const { return
(**this).second; }
++ };
++
++ typedef accessor_wrapper<base_type::const_iterator> const_accessor;
++ typedef accessor_wrapper<base_type::iterator> accessor;
++
++ // Add a potential node to contact for the search.
++ bool add_contact(const HashString& id, const
rak::socket_address* sa);
++ void add_contacts(const DhtBucket& contacts);
++
++ // Return next node to contact. Up to concurrent_searches nodes are
returned,
++ // and end() after that. Don't advance the accessor to get further
contacts!
++ const_accessor get_contact();
++
++ // Search statistics.
++ int num_contacted() { return
m_contacted; }
++ int num_replied() { return
m_replied; }
++
++ bool start() { m_started =
true; return m_pending; }
++ bool complete() const { return m_started
&& !m_pending; }
++
++ const HashString& target() const { return
key_comp().target(); }
++
++ virtual bool is_announce() const { return false; }
++
++ // Expose the otherwise private end() function but return an accessor,
++ // to allow code checking whether get_contact returned a valid accessor.
++ const_accessor end() const { return
base_type::end(); }
++
++ // Used by the sorting/comparison predicate to see which node is closer.
++ static bool is_closer(const HashString& one, const HashString&
two, const HashString& target);
++
++protected:
++ void trim(bool final);
++ void node_status(const_accessor& n, bool success);
++ void set_node_active(const_accessor& n, bool active);
++
++ // Statistics about contacted nodes.
++ unsigned int m_pending;
++ unsigned int m_contacted;
++ unsigned int m_replied;
++ unsigned int m_concurrency;
++
++ bool m_restart; // If true, trim nodes and reset m_next
on the following get_contact call.
++ bool m_started;
++
++ // Next node to return in get_contact, is end() if we have no more
contactable nodes.
++ const_accessor m_next;
++
++private:
++ DhtSearch(const DhtSearch& s);
++
++ bool node_uncontacted(const DhtNode* node) const;
++};
++
++class DhtAnnounce : public DhtSearch {
++public:
++ DhtAnnounce(const HashString& infoHash, TrackerDht* tracker, const
DhtBucket& contacts)
++ : DhtSearch(infoHash, contacts),
++ m_tracker(tracker) { }
++
++ TrackerDht* tracker() const { return
m_tracker; }
++
++ // Start announce and return final set of nodes in get_contact() calls.
++ // This resets DhtSearch's completed() function, which now
++ // counts announces instead.
++ const_accessor start_announce();
++
++ virtual bool is_announce() const { return true; }
++
++private:
++ TrackerDht* m_tracker;
++};
++
++// Class holding transaction data to be transmitted.
++class DhtTransactionPacket {
++public:
++ // transaction packet
++ DhtTransactionPacket(const rak::socket_address* s, const Object& d,
unsigned int id, DhtTransaction* t)
++ : m_sa(*s), m_id(id), m_transaction(t) { build_buffer(d); };
++
++ // non-transaction packet
++ DhtTransactionPacket(const rak::socket_address* s, const Object& d)
++ : m_sa(*s), m_id(-cachedTime.seconds()), m_transaction(NULL) {
build_buffer(d); };
++
++ ~DhtTransactionPacket() { delete[] m_data; }
++
++ bool has_transaction() const { return m_id >=
-1; }
++ bool has_failed() const { return m_id ==
-1; }
++ void set_failed() { m_id = -1; }
++
++ const rak::socket_address* address() const { return &m_sa; }
++ rak::socket_address* address() { return &m_sa; }
++
++ const char* c_str() const { return m_data; }
++ size_t length() const { return m_length; }
++
++ int id() const { return m_id; }
++ int age() const { return
has_transaction() ? 0 : cachedTime.seconds() + m_id; }
++ const DhtTransaction* transaction() const { return
m_transaction; }
++ DhtTransaction* transaction() { return
m_transaction; }
++
++private:
++ void build_buffer(const Object& data);
++
++ rak::socket_address m_sa;
++ char* m_data;
++ size_t m_length;
++ int m_id;
++ DhtTransaction* m_transaction;
++};
++
++// DHT Transaction classes. DhtTransaction and DhtTransactionSearch
++// are not directly usable with no public constructor, since type()
++// is a pure virtual function.
++class DhtTransaction {
++public:
++ virtual ~DhtTransaction();
++
++ typedef enum {
++ DHT_PING,
++ DHT_FIND_NODE,
++ DHT_GET_PEERS,
++ DHT_ANNOUNCE_PEER,
++ } transaction_type;
++
++ virtual transaction_type type() = 0;
++ virtual bool is_search() { return false; }
++
++ // Key to uniquely identify a transaction with given per-node transaction
id.
++ uint64_t key(int id) const { return key(&m_sa, id);
}
++ static uint64_t key(const rak::socket_address* sa, int id) {
return ((uint64_t)sa->sa_inet()->address_n() << 32) + id; }
++
++ // Node ID and address.
++ const HashString& id() { return m_id; }
++ const rak::socket_address* address() { return &m_sa; }
++
++ int timeout() { return m_timeout; }
++ int quick_timeout() { return m_quickTimeout;
}
++ bool has_quick_timeout() { return
m_hasQuickTimeout; }
++
++ int dec_retry() { return m_retry--; }
++ int retry() { return m_retry; }
++
++ DhtTransactionPacket* packet() { return m_packet; }
++ void set_packet(DhtTransactionPacket* p) {
m_packet = p; }
++
++ // These could (should?) check that the type matches. Or use dynamic cast.
++ DhtTransactionSearch* as_search() { return
reinterpret_cast<DhtTransactionSearch*>(this); }
++ DhtTransactionPing* as_ping() { return
reinterpret_cast<DhtTransactionPing*>(this); }
++ DhtTransactionFindNode* as_find_node() { return
reinterpret_cast<DhtTransactionFindNode*>(this); }
++ DhtTransactionGetPeers* as_get_peers() { return
reinterpret_cast<DhtTransactionGetPeers*>(this); }
++ DhtTransactionAnnounce* as_announce() { return
reinterpret_cast<DhtTransactionAnnounce*>(this); }
++
++protected:
++ DhtTransaction(int quick_timeout, int timeout, const HashString& id,
const rak::socket_address* sa);
++
++ bool m_hasQuickTimeout;
++
++private:
++ DhtTransaction(const DhtTransaction& t);
++
++ HashString m_id;
++ rak::socket_address m_sa;
++ int m_timeout;
++ int m_quickTimeout;
++ int m_retry;
++ DhtTransactionPacket* m_packet;
++};
++
++class DhtTransactionSearch : public DhtTransaction {
++public:
++ virtual ~DhtTransactionSearch();
++
++ virtual bool is_search() { return true; }
++
++ DhtSearch::const_accessor node() { return m_node; }
++ DhtSearch* search() { return
m_search; }
++
++ void set_stalled();
++
++ void complete(bool success);
++
++protected:
++ DhtTransactionSearch(int quick_timeout, int timeout,
DhtSearch::const_accessor& node)
++ : DhtTransaction(quick_timeout, timeout, node.node()->id(),
node.node()->address()),
++ m_node(node),
++ m_search(node.search()) { if (!m_hasQuickTimeout)
m_search->m_concurrency++; }
++
++private:
++ DhtSearch::const_accessor m_node;
++ DhtSearch* m_search;
++};
++
++// Actual transaction classes.
++class DhtTransactionPing : public DhtTransaction {
++public:
++ DhtTransactionPing(const HashString& id, const rak::socket_address* sa)
++ : DhtTransaction(-1, 30, id, sa) { }
++
++ virtual transaction_type type() { return DHT_PING;
}
++};
++
++class DhtTransactionFindNode : public DhtTransactionSearch {
++public:
++ DhtTransactionFindNode(DhtSearch::const_accessor& node)
++ : DhtTransactionSearch(4, 30, node) { }
++
++ virtual transaction_type type() { return
DHT_FIND_NODE; }
++};
++
++class DhtTransactionGetPeers : public DhtTransactionSearch {
++public:
++ DhtTransactionGetPeers(DhtSearch::const_accessor& node)
++ : DhtTransactionSearch(-1, 30, node) { }
++
++ virtual transaction_type type() { return
DHT_GET_PEERS; }
++
++ DhtAnnounce* announce() { return
reinterpret_cast<DhtAnnounce*>(search()); }
++};
++
++class DhtTransactionAnnounce : public DhtTransaction {
++public:
++ DhtTransactionAnnounce(const HashString& id, const rak::socket_address*
sa, const HashString& infoHash, const std::string& token)
++ : DhtTransaction(-1, 30, id, sa),
++ m_infoHash(infoHash),
++ m_token(token) { }
++
++ virtual transaction_type type() { return
DHT_ANNOUNCE_PEER; }
++
++ const HashString& info_hash() { return
m_infoHash; }
++ const std::string& token() { return m_token; }
++
++private:
++ HashString m_infoHash;
++ std::string m_token;
++};
++
++inline bool
++DhtSearch::is_closer(const HashString& one, const HashString& two, const
HashString& target) {
++ for (unsigned int i=0; i<one.size(); i++)
++ if (one[i] != two[i])
++ return (uint8_t)(one[i] ^ target[i]) < (uint8_t)(two[i] ^ target[i]);
++
++ return false;
++}
++
++inline void
++DhtSearch::set_node_active(const_accessor& n, bool active) {
++ n.node()->m_lastSeen = active;
++}
++
++inline bool
++dht_compare_closer::operator () (const DhtNode* one, const DhtNode* two)
const {
++ return DhtSearch::is_closer(*one, *two, m_target);
++}
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_node.h
+===================================================================
+--- libtorrent/src/dht/dht_node.h (revision 0)
++++ libtorrent/src/dht/dht_node.h (revision 0)
+@@ -0,0 +1,136 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_NODE_H
++#define LIBTORRENT_DHT_NODE_H
++
++#include "globals.h"
++
++#include <rak/socket_address.h>
++
++#include "torrent/hash_string.h"
++
++#include "dht_bucket.h"
++
++namespace torrent {
++
++class DhtBucket;
++
++class DhtNode : public HashString {
++ friend class DhtSearch;
++
++public:
++ // A node is considered bad if it failed to reply to this many queries.
++ static const unsigned int max_failed_replies = 5;
++
++ DhtNode(const HashString& id, const rak::socket_address* sa);
++ DhtNode(const std::string& id, const Object& cache);
++
++ const HashString& id() const { return *this; }
++ const rak::socket_address* address() const { return
&m_socketAddress; }
++ void set_address(const rak::socket_address* sa) {
m_socketAddress = *sa; }
++
++ // For determining node quality.
++ unsigned int last_seen() const { return
m_lastSeen; }
++ unsigned int age() const { return
cachedTime.seconds() - m_lastSeen; }
++ bool is_good() const { return
m_recentlyActive; }
++ bool is_questionable() const { return
!m_recentlyActive; }
++ bool is_bad() const { return
m_recentlyInactive >= max_failed_replies; };
++ bool is_active() const { return
m_lastSeen; }
++
++ // Update is called once every 15 minutes.
++ void update() { m_recentlyActive
= age() < 15 * 60; }
++
++ // Called when node replies to us, queries us, or fails to reply.
++ void replied() { set_good(); }
++ void queried() { if (m_lastSeen)
set_good(); }
++ void inactive();
++
++ DhtBucket* bucket() const { return m_bucket;
}
++ DhtBucket* set_bucket(DhtBucket* b) { m_bucket = b;
return b; }
++
++ bool is_in_range(const DhtBucket* b) { return
b->is_in_range(*this); }
++
++ // Store compact node information (26 bytes address, port and ID) in the
given
++ // buffer and return pointer to end of stored information.
++ char* store_compact(char* buffer) const;
++
++ // Store node cache in the given container object and return it.
++ Object* store_cache(Object* container) const;
++
++private:
++ DhtNode();
++
++ void set_good();
++ void set_bad();
++
++ rak::socket_address m_socketAddress;
++ unsigned int m_lastSeen;
++ bool m_recentlyActive;
++ unsigned int m_recentlyInactive;
++ DhtBucket* m_bucket;
++};
++
++inline void
++DhtNode::set_good() {
++ if (m_bucket != NULL && !is_good())
++ m_bucket->node_now_good(is_bad());
++
++ m_lastSeen = cachedTime.seconds();
++ m_recentlyInactive = 0;
++ m_recentlyActive = true;
++}
++
++inline void
++DhtNode::set_bad() {
++ if (m_bucket != NULL && !is_bad())
++ m_bucket->node_now_bad(is_good());
++
++ m_recentlyInactive = max_failed_replies;
++ m_recentlyActive = false;
++}
++
++inline void
++DhtNode::inactive() {
++ if (m_recentlyInactive + 1 == max_failed_replies)
++ set_bad();
++ else
++ m_recentlyInactive++;
++}
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_server.h
+===================================================================
+--- libtorrent/src/dht/dht_server.h (revision 0)
++++ libtorrent/src/dht/dht_server.h (revision 0)
+@@ -0,0 +1,186 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_DHT_SERVER_H
++#define LIBTORRENT_DHT_SERVER_H
++
++#include <map>
++#include <deque>
++#include <rak/priority_queue_default.h>
++#include <rak/socket_address.h>
++
++#include "net/socket_datagram.h"
++#include "net/throttle_node.h"
++#include "download/download_info.h" // for SocketAddressCompact
++#include "torrent/hash_string.h"
++
++namespace torrent {
++
++class DhtBucket;
++class DhtNode;
++class DhtRouter;
++
++class DownloadInfo;
++class TrackerDht;
++
++class DhtTransaction;
++class DhtTransactionPacket;
++class DhtTransactionSearch;
++class DhtTransactionGetPeers;
++
++// UDP server that handles the DHT node communications.
++
++class DhtServer : public SocketDatagram {
++public:
++ DhtServer(DhtRouter* self);
++ ~DhtServer();
++
++ void start(int port);
++ void stop();
++ bool is_active() const { return
get_fd().is_valid(); }
++
++ unsigned int queries_received() const { return
m_queriesReceived; }
++ unsigned int queries_sent() const { return
m_queriesSent; }
++ unsigned int replies_received() const { return
m_repliesReceived; }
++ void reset_statistics();
++
++ // Contact a node to see if it replies. Set id=0 if unknown.
++ void ping(const HashString& id, const rak::socket_address*
sa);
++
++ // Do a find_node search with the given contacts as starting point for the
++ // search.
++ void find_node(const DhtBucket& contacts, const
HashString& target);
++
++ // Do DHT announce, starting with the given contacts.
++ void announce(const DhtBucket& contacts, const HashString&
infoHash, TrackerDht* tracker);
++
++ // Cancel given announce for given tracker, or all matching announces if
info/tracker NULL.
++ void cancel_announce(DownloadInfo* info, const TrackerDht*
tracker);
++
++ // Called every 15 minutes.
++ void update();
++
++ ThrottleNode* upload_throttle() { return
&m_uploadThrottle; }
++ const ThrottleNode* upload_throttle() const { return
&m_uploadThrottle; }
++ ThrottleNode* download_throttle() { return
&m_downloadThrottle; }
++ const ThrottleNode* download_throttle() const { return
&m_downloadThrottle; }
++
++ virtual void event_read();
++ virtual void event_write();
++ virtual void event_error();
++
++private:
++ // DHT error codes.
++ static const int dht_error_generic = 201;
++ static const int dht_error_server = 202;
++ static const int dht_error_protocol = 203;
++ static const int dht_error_bad_method = 204;
++
++ typedef std::deque<DhtTransactionPacket*> packet_queue;
++
++ struct compact_node_info {
++ char _id[20];
++ SocketAddressCompact _addr;
++
++ HashString& id() { return
*HashString::cast_from(_id); }
++ rak::socket_address address() { return rak::socket_address(_addr);
}
++ } __attribute__ ((packed));
++ typedef std::list<compact_node_info> node_info_list;
++
++ // Pending transactions (using DhtTransaction::key as key).
++ typedef std::map<uint64_t, DhtTransaction*> transaction_map;
++ typedef transaction_map::iterator transaction_itr;
++
++ // DHT transaction names for given transaction type.
++ static const char* queries[];
++
++ // Priorities for the outgoing packets.
++ static const int packet_prio_high = 2; // For important queries we send
(announces).
++ static const int packet_prio_low = 1; // For (relatively) unimportant
queries we send.
++ static const int packet_prio_reply = 0; // For replies to peer queries.
++
++ void start_write();
++
++ void process_query(const Object& transaction, const
HashString& id, const rak::socket_address* sa, Object& req);
++ void process_response(int transaction, const HashString&
id, const rak::socket_address* sa, Object& req);
++ void process_error(int transaction, const
rak::socket_address* sa, Object& req);
++
++ void parse_node_info(node_info_list& l, const std::string&
n);
++ void parse_find_node_reply(DhtTransactionSearch* t, const
std::string& nodes);
++ void parse_get_peers_reply(DhtTransactionGetPeers* t,
const Object& res);
++
++ void find_node_next(DhtTransactionSearch* t);
++
++ void add_packet(DhtTransactionPacket* packet, int
priority);
++ void create_query(transaction_itr itr, int tID, const
rak::socket_address* sa, int priority);
++ void create_response(const Object& transaction, const
rak::socket_address* sa, Object& r);
++ void create_error(const Object& transaction, const
rak::socket_address* sa, int num, const std::string& msg);
++
++ void create_find_node_response(const Object& arg, Object&
reply);
++ void create_get_peers_response(const Object& arg, const
rak::socket_address* sa, Object& reply);
++ void create_announce_peer_response(const Object& arg,
const rak::socket_address* sa, Object& reply);
++
++ int add_transaction(DhtTransaction* t, int priority);
++
++ // These methods return the iterator after the given one or end()
++ transaction_itr failed_transaction(transaction_itr itr, bool quick);
++ transaction_itr cancel_transaction(transaction_itr itr);
++
++ void clear_transactions();
++
++ bool process_queue(packet_queue& queue, uint32_t* quota);
++ void receive_timeout();
++
++ DhtRouter* m_router;
++ packet_queue m_highQueue;
++ packet_queue m_lowQueue;
++ transaction_map m_transactions;
++
++ rak::priority_item m_taskTimeout;
++
++ ThrottleNode m_uploadThrottle;
++ ThrottleNode m_downloadThrottle;
++
++ unsigned int m_queriesReceived;
++ unsigned int m_queriesSent;
++ unsigned int m_repliesReceived;
++
++ bool m_networkUp;
++};
++
++}
++
++#endif
+Index: libtorrent/src/dht/dht_tracker.cc
+===================================================================
+--- libtorrent/src/dht/dht_tracker.cc (revision 0)
++++ libtorrent/src/dht/dht_tracker.cc (revision 0)
+@@ -0,0 +1,129 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include "torrent/object.h"
++
++#include "dht_tracker.h"
++
++namespace torrent {
++
++void
++DhtTracker::add_peer(const rak::socket_address* sa) {
++ // Should check that sa is an INET address.
++ SocketAddressCompact compact(sa->sa_inet()->address_n(),
sa->sa_inet()->port_n());
++
++ unsigned int oldest = 0;
++ uint32_t minSeen = ~uint32_t();
++
++ // Check if peer exists. If not, find oldest peer.
++ for (unsigned int i = 0; i < size(); i++) {
++ if ((*this)[i].addr == compact.addr) {
++ (*this)[i].port = compact.port;
++ m_lastSeen[i] = cachedTime.seconds();
++ return;
++
++ } else if (m_lastSeen[i] < minSeen) {
++ minSeen = m_lastSeen[i];
++ oldest = i;
++ }
++ }
++
++ // If peer doesn't exist, append to list if the table is not full.
++ if (size() < max_size) {
++ push_back(compact);
++ m_lastSeen.push_back(cachedTime.seconds());
++
++ // Peer doesn't exist and table is full: replace oldest peer.
++ } else {
++ (*this)[oldest] = compact;
++ m_lastSeen[oldest] = cachedTime.seconds();
++ }
++}
++
++// Return compact info (6 bytes) for up to 30 peers, returning different
++// peers for each call if there are more.
++std::string
++DhtTracker::get_peers(unsigned int maxPeers) {
++ iterator first;
++ iterator last;
++
++ // If we have more than max_peers, randomly return block of peers.
++ // The peers in overlapping blocks get picked twice as often, but
++ // that's better than returning fewer peers.
++ if (size() > maxPeers) {
++ unsigned int blocks = (size() + maxPeers - 1) / maxPeers;
++
++ first = begin() + (random() % blocks) * (size() - maxPeers) / (blocks -
1);
++ last = first + maxPeers;
++
++ } else {
++ first = begin();
++ last = end();
++ }
++
++ return std::string(first->c_str(), last->c_str());
++}
++
++// Remove old announces.
++void
++DhtTracker::prune(uint32_t maxAge) {
++ uint32_t minSeen = cachedTime.seconds() - maxAge;
++ unsigned int i;
++
++ for (i = 0; i < size(); i++)
++ if (m_lastSeen[i] <= minSeen)
++ break;
++
++ if (i == size())
++ return; // No old peers found.
++
++ unsigned int index = i;
++
++ for (i++; i < size(); i++) {
++ if (m_lastSeen[i] > minSeen) {
++ m_lastSeen[index] = m_lastSeen[i];
++ (*this)[index] = (*this)[i];
++ index++;
++ }
++ }
++
++ resize(index);
++ m_lastSeen.resize(index);
++}
++
++}
+Index: libtorrent/src/net/throttle_node.h
+===================================================================
+--- libtorrent/src/net/throttle_node.h (revision 975)
++++ libtorrent/src/net/throttle_node.h (working copy)
+@@ -45,13 +45,13 @@
+
+ namespace torrent {
+
+-class PeerConnectionBase;
++class SocketBase;
+
+ class ThrottleNode {
+ public:
+ typedef ThrottleList::iterator iterator;
+ typedef ThrottleList::const_iterator const_iterator;
+- typedef rak::mem_fun0<PeerConnectionBase, void> SlotActivate;
++ typedef rak::mem_fun0<SocketBase, void> SlotActivate;
+
+ ThrottleNode(uint32_t rateSpan) : m_rate(rateSpan) { clear_quota(); }
+
+Index: libtorrent/src/net/socket_base.cc
+===================================================================
+--- libtorrent/src/net/socket_base.cc (revision 975)
++++ libtorrent/src/net/socket_base.cc (working copy)
+@@ -41,6 +41,8 @@
+ #include <sys/socket.h>
+
+ #include "torrent/exceptions.h"
++#include "torrent/poll.h"
++#include "manager.h"
+ #include "socket_base.h"
+
+ namespace torrent {
+@@ -72,4 +74,14 @@
+ return r == 1;
+ }
+
++void
++SocketBase::receive_throttle_down_activate() {
++ manager->poll()->insert_read(this);
++}
++
++void
++SocketBase::receive_throttle_up_activate() {
++ manager->poll()->insert_write(this);
++}
++
+ } // namespace torrent
+Index: libtorrent/src/net/socket_base.h
+===================================================================
+--- libtorrent/src/net/socket_base.h (revision 975)
++++ libtorrent/src/net/socket_base.h (working copy)
+@@ -60,6 +60,9 @@
+ bool read_oob(void* buffer);
+ bool write_oob(const void* buffer);
+
++ void receive_throttle_down_activate();
++ void receive_throttle_up_activate();
++
+ protected:
+ // Disable copying
+ SocketBase(const SocketBase&);
+Index: libtorrent/src/tracker/tracker_control.cc
+===================================================================
+--- libtorrent/src/tracker/tracker_control.cc (revision 975)
++++ libtorrent/src/tracker/tracker_control.cc (working copy)
+@@ -43,6 +43,7 @@
+ #include "net/address_list.h"
+ #include "torrent/exceptions.h"
+ #include "tracker_control.h"
++#include "tracker_dht.h"
+ #include "tracker_http.h"
+ #include "tracker_udp.h"
+
+@@ -73,6 +74,9 @@
+ else if (std::strncmp("udp://", url.c_str(), 6) == 0)
+ t = new TrackerUdp(m_info, url);
+
++ else if (std::strncmp("dht://", url.c_str(), 6) == 0)
++ t = new TrackerDht(m_info, url);
++
+ else
+ // TODO: Error message here?... not really...
+ return;
+@@ -101,7 +105,7 @@
+ m_tries = -1;
+ m_state = s;
+
+- m_itr = m_list.find_enabled(m_itr);
++ m_itr = m_list.find_usable(m_itr);
+
+ if (m_itr != m_list.end())
+ m_itr->second->send_state(m_state,
+Index: libtorrent/src/tracker/tracker_dht.h
+===================================================================
+--- libtorrent/src/tracker/tracker_dht.h (revision 0)
++++ libtorrent/src/tracker/tracker_dht.h (revision 0)
+@@ -0,0 +1,72 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef LIBTORRENT_TRACKER_TRACKER_DHT_H
++#define LIBTORRENT_TRACKER_TRACKER_DHT_H
++
++#include "net/address_list.h"
++#include "torrent/object.h"
++#include "tracker_base.h"
++
++namespace torrent {
++
++class TrackerDht : public TrackerBase {
++public:
++ TrackerDht(DownloadInfo* info, const std::string& url);
++ ~TrackerDht();
++
++ virtual bool is_busy() const;
++ virtual bool is_usable() const;
++
++ virtual void send_state(DownloadInfo::State state, uint64_t down,
uint64_t up, uint64_t left);
++ virtual void close();
++
++ virtual Type type() const;
++
++ bool has_peers() const { return
!m_peers.empty(); }
++
++ void receive_peers(const std::string& peers);
++ void receive_success();
++ void receive_failed(const std::string& msg);
++ void receive_status(int replied, int contacted, const
char* stage);
++
++private:
++ AddressList m_peers;
++};
++
++}
++
++#endif
+Index: libtorrent/src/tracker/tracker_container.cc
+===================================================================
+--- libtorrent/src/tracker/tracker_container.cc (revision 975)
++++ libtorrent/src/tracker/tracker_container.cc (working copy)
+@@ -47,10 +47,13 @@
+
+ namespace torrent {
+
++struct tracker_usable_t : public
std::unary_function<TrackerContainer::value_type, bool> {
++ bool operator () (const TrackerContainer::value_type& value) const {
return value.second->is_usable(); }
++};
++
+ bool
+-TrackerContainer::has_enabled() const {
+- return std::find_if(begin(), end(),
+- rak::on(rak::mem_ref(&value_type::second),
std::mem_fun(&TrackerBase::is_enabled)))
++TrackerContainer::has_usable() const {
++ return std::find_if(begin(), end(), tracker_usable_t())
+ != end();
+ }
+
+@@ -99,8 +102,8 @@
+ }
+
+ TrackerContainer::iterator
+-TrackerContainer::find_enabled(iterator itr) {
+- while (itr != end() && !itr->second->is_enabled())
++TrackerContainer::find_usable(iterator itr) {
++ while (itr != end() && !tracker_usable_t()(*itr))
+ ++itr;
+
+ return itr;
+Index: libtorrent/src/tracker/tracker_container.h
+===================================================================
+--- libtorrent/src/tracker/tracker_container.h (revision 975)
++++ libtorrent/src/tracker/tracker_container.h (working copy)
+@@ -72,7 +72,7 @@
+
+ ~TrackerContainer() { clear(); }
+
+- bool has_enabled() const;
++ bool has_usable() const;
+
+ void randomize();
+ void clear();
+@@ -82,7 +82,7 @@
+ iterator promote(iterator itr);
+
+ iterator find(TrackerBase* tb);
+- iterator find_enabled(iterator itr);
++ iterator find_usable(iterator itr);
+ const_iterator find_enabled(const_iterator itr) const;
+
+ iterator begin_group(int group);
+Index: libtorrent/src/tracker/tracker_udp.cc
+===================================================================
+--- libtorrent/src/tracker/tracker_udp.cc (revision 975)
++++ libtorrent/src/tracker/tracker_udp.cc (working copy)
+@@ -36,9 +36,6 @@
+
+ #include "config.h"
+
+-#include <sigc++/bind.h>
+-#include <torrent/connection_manager.h>
+-
+ #include "net/address_list.h"
+ #include "torrent/exceptions.h"
+ #include "torrent/connection_manager.h"
+@@ -64,7 +61,7 @@
+
+ close();
+ }
+-
++
+ bool
+ TrackerUdp::is_busy() const {
+ return get_fd().is_valid();
+@@ -91,7 +88,7 @@
+ m_sendUp = up;
+ m_sendLeft = left;
+
+- m_slotResolver = manager->connection_manager()->resolver()(hostname,
PF_INET, SOCK_DGRAM,
++ m_slotResolver = manager->connection_manager()->resolver()(hostname,
(int)rak::socket_address::pf_inet, SOCK_DGRAM,
+
sigc::mem_fun(this, &TrackerUdp::start_announce));
+ }
+
+Index: libtorrent/src/tracker/Makefile.am
+===================================================================
+--- libtorrent/src/tracker/Makefile.am (revision 975)
++++ libtorrent/src/tracker/Makefile.am (working copy)
+@@ -4,6 +4,8 @@
+ tracker_base.h \
+ tracker_control.cc \
+ tracker_control.h \
++ tracker_dht.cc \
++ tracker_dht.h \
+ tracker_http.cc \
+ tracker_http.h \
+ tracker_container.cc \
+Index: libtorrent/src/tracker/tracker_base.h
+===================================================================
+--- libtorrent/src/tracker/tracker_base.h (revision 975)
++++ libtorrent/src/tracker/tracker_base.h (working copy)
+@@ -58,7 +58,8 @@
+ typedef enum {
+ TRACKER_NONE,
+ TRACKER_HTTP,
+- TRACKER_UDP
++ TRACKER_UDP,
++ TRACKER_DHT
+ } Type;
+
+ TrackerBase(DownloadInfo* info, const std::string& url) :
+@@ -69,6 +70,7 @@
+
+ virtual bool is_busy() const = 0;
+ bool is_enabled() const { return
m_enabled; }
++ virtual bool is_usable() const { return
m_enabled; }
+
+ void enable(bool state) { m_enabled =
state; }
+
+@@ -81,6 +83,8 @@
+ const std::string& url() const { return m_url;
}
+ void set_url(const std::string& url) { m_url = url; }
+
++ const std::string& status() const { return
m_status; }
++
+ const std::string& tracker_id() const { return
m_trackerId; }
+ void set_tracker_id(const std::string& id) { m_trackerId =
id; }
+
+@@ -106,6 +110,7 @@
+
+ DownloadInfo* m_info;
+ std::string m_url;
++ std::string m_status;
+
+ std::string m_trackerId;
+
+Index: libtorrent/src/tracker/tracker_dht.cc
+===================================================================
+--- libtorrent/src/tracker/tracker_dht.cc (revision 0)
++++ libtorrent/src/tracker/tracker_dht.cc (revision 0)
+@@ -0,0 +1,151 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include <sstream>
++
++#include "dht/dht_router.h"
++#include "torrent/connection_manager.h"
++#include "torrent/dht_manager.h"
++#include "torrent/exceptions.h"
++
++#include "tracker_dht.h"
++#include "tracker_http.h"
++
++#include "globals.h"
++#include "manager.h"
++
++namespace torrent {
++
++TrackerDht::TrackerDht(DownloadInfo* info, const std::string& url) :
++ TrackerBase(info, url) {
++
++ if (!manager->dht_manager()->is_valid())
++ throw internal_error("Trying to add DHT tracker with no DHT manager.");
++}
++
++TrackerDht::~TrackerDht() {
++ if (is_busy())
++ manager->dht_manager()->router()->cancel_announce(NULL, this);
++}
++
++bool
++TrackerDht::is_busy() const {
++ return !m_status.empty();
++}
++
++bool
++TrackerDht::is_usable() const {
++ return m_enabled && manager->dht_manager()->is_active();
++}
++
++void
++TrackerDht::send_state(DownloadInfo::State state, uint64_t down, uint64_t
up, uint64_t left) {
++ if (m_info == NULL)
++ throw internal_error("TrackerDht::send_state(...) does not have a valid
m_info.");
++
++ if (is_busy()) {
++ manager->dht_manager()->router()->cancel_announce(m_info, this);
++
++ if (is_busy())
++ throw internal_error("TrackerDht::send_state cancel_announce did not
cancel announce.");
++ }
++
++ if (state == DownloadInfo::STOPPED)
++ return;
++
++ m_status = "[Initializing]";
++
++ if (!manager->dht_manager()->is_active())
++ return receive_failed("DHT server not active.");
++
++ manager->dht_manager()->router()->announce(m_info, this);
++
++ set_normal_interval(20 * 60);
++ set_min_interval(0);
++}
++
++void
++TrackerDht::close() {
++ if (is_busy())
++ manager->dht_manager()->router()->cancel_announce(m_info, this);
++}
++
++TrackerDht::Type
++TrackerDht::type() const {
++ return TRACKER_DHT;
++}
++
++void
++TrackerDht::receive_peers(const std::string& peers) {
++ if (!is_busy())
++ throw internal_error("TrackerDht::receive_peers called while not
busy.");
++
++ m_peers.parse_address_compact(peers);
++}
++
++void
++TrackerDht::receive_success() {
++ if (!is_busy())
++ throw internal_error("TrackerDht::receive_success called while not
busy.");
++
++ m_status.clear();
++ m_slotSuccess(this, &m_peers);
++ m_peers.clear();
++}
++
++void
++TrackerDht::receive_failed(const std::string& msg) {
++ if (!is_busy())
++ throw internal_error("TrackerDht::receive_failed called while not
busy.");
++
++ m_status.clear();
++ m_slotFailed(this, msg);
++ m_peers.clear();
++}
++
++void
++TrackerDht::receive_status(int replied, int contacted, const char* stage) {
++ if (!is_busy())
++ throw internal_error("TrackerDht::receive_status called while not
busy.");
++
++ std::stringstream s;
++ s << "[" << stage << ": " << replied << "/" << contacted << " nodes
replied]";
++ m_status = s.str();
++}
++
++}
+Index: libtorrent/src/torrent/tracker.cc
+===================================================================
+--- libtorrent/src/torrent/tracker.cc (revision 975)
++++ libtorrent/src/torrent/tracker.cc (working copy)
+@@ -46,6 +46,11 @@
+ return m_tracker.second->is_enabled();
+ }
+
++bool
++Tracker::is_usable() const {
++ return m_tracker.second->is_usable();
++}
++
+ void
+ Tracker::enable() {
+ m_tracker.second->enable(true);
+@@ -67,6 +72,11 @@
+ }
+
+ const std::string&
++Tracker::status() const {
++ return m_tracker.second->status();
++}
++
++const std::string&
+ Tracker::tracker_id() const {
+ return m_tracker.second->tracker_id();
+ }
+Index: libtorrent/src/torrent/torrent.cc
+===================================================================
+--- libtorrent/src/torrent/torrent.cc (revision 975)
++++ libtorrent/src/torrent/torrent.cc (working copy)
+@@ -165,6 +165,11 @@
+ return manager->connection_manager();
+ }
+
++DhtManager*
++dht_manager() {
++ return manager->dht_manager();
++}
++
+ uint32_t
+ total_handshakes() {
+ return manager->handshake_manager()->size();
+Index: libtorrent/src/torrent/tracker.h
+===================================================================
+--- libtorrent/src/torrent/tracker.h (revision 975)
++++ libtorrent/src/torrent/tracker.h (working copy)
+@@ -58,6 +58,7 @@
+ Tracker(value_type v) : m_tracker(v) {}
+
+ bool is_enabled() const;
++ bool is_usable() const;
+ bool is_open() const;
+
+ void enable();
+@@ -65,6 +66,7 @@
+
+ uint32_t group() const { return m_tracker.first; }
+ const std::string& url() const;
++ const std::string& status() const;
+
+ // The "tracker id" string returned by the tracker.
+ const std::string& tracker_id() const;
+Index: libtorrent/src/torrent/torrent.h
+===================================================================
+--- libtorrent/src/torrent/torrent.h (revision 975)
++++ libtorrent/src/torrent/torrent.h (working copy)
+@@ -65,6 +65,7 @@
+ ChunkManager* chunk_manager() LIBTORRENT_EXPORT;
+ ClientList* client_list() LIBTORRENT_EXPORT;
+ ConnectionManager* connection_manager() LIBTORRENT_EXPORT;
++DhtManager* dht_manager() LIBTORRENT_EXPORT;
+
+ uint32_t total_handshakes() LIBTORRENT_EXPORT;
+
+Index: libtorrent/src/torrent/peer/peer_info.h
+===================================================================
+--- libtorrent/src/torrent/peer/peer_info.h (revision 975)
++++ libtorrent/src/torrent/peer/peer_info.h (working copy)
+@@ -80,6 +80,7 @@
+ uint32_t last_connection() const { return
m_lastConnection; }
+ void set_last_connection(uint32_t tvsec) {
m_lastConnection = tvsec; }
+
++ bool supports_dht() const { return
m_options[7] & 0x01; }
+ bool supports_extensions() const { return
m_options[5] & 0x10; }
+
+ // Internal to libTorrent:
+Index: libtorrent/src/torrent/dht_manager.cc
+===================================================================
+--- libtorrent/src/torrent/dht_manager.cc (revision 0)
++++ libtorrent/src/torrent/dht_manager.cc (revision 0)
+@@ -0,0 +1,119 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include <rak/address_info.h>
++#include <rak/socket_address.h>
++
++#include <torrent/exceptions.h>
++
++#include "manager.h"
++
++#include "dht/dht_router.h"
++#include "dht/dht_server.h"
++#include "dht_manager.h"
++#include "net/throttle_manager.h"
++
++namespace torrent {
++
++DhtManager::~DhtManager() {
++ stop();
++ delete m_router;
++}
++
++void
++DhtManager::initialize(const Object& dhtCache) {
++ if (m_router != NULL)
++ throw internal_error("DhtManager::initialize called with DHT already
active.");
++
++ m_router = new DhtRouter(dhtCache,
rak::socket_address::cast_from(manager->connection_manager()->bind_address()));
++}
++
++void
++DhtManager::start(port_type port) {
++ if (m_router == NULL)
++ throw internal_error("DhtManager::start called without initializing
first.");
++
++ m_router->start(port);
++}
++
++
++void
++DhtManager::stop() {
++ if (m_router != NULL)
++ m_router->stop();
++}
++
++bool
++DhtManager::is_active() const {
++ return m_router != NULL && m_router->is_active();
++}
++
++void
++DhtManager::add_node(const sockaddr* addr, int port, bool external) {
++ if (m_router == NULL)
++ return;
++
++ rak::socket_address sa = *rak::socket_address::cast_from(addr);
++
++ if (sa.family() != rak::socket_address::af_inet)
++ return;
++
++
++ sa.set_port(port);
++ m_router->contact(&sa, external);
++}
++
++Object*
++DhtManager::store_cache(Object* container) const {
++ if (m_router == NULL)
++ throw internal_error("DhtManager::store_cache called but DHT not
initialized.");
++
++ return m_router->store_cache(container);
++}
++
++DhtManager::statistics_type
++DhtManager::get_statistics() const {
++ return m_router->get_statistics();
++}
++
++void
++DhtManager::reset_statistics() {
++ m_router->reset_statistics();
++}
++
++}
+Index: libtorrent/src/torrent/dht_manager.h
+===================================================================
+--- libtorrent/src/torrent/dht_manager.h (revision 0)
++++ libtorrent/src/torrent/dht_manager.h (revision 0)
+@@ -0,0 +1,125 @@
++// libTorrent - BitTorrent library
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++// Add some helpfull words here.
++
++#ifndef LIBTORRENT_DHT_MANAGER_H
++#define LIBTORRENT_DHT_MANAGER_H
++
++#include <torrent/common.h>
++#include <torrent/connection_manager.h>
++
++namespace torrent {
++
++class ThrottleList;
++
++class LIBTORRENT_EXPORT DhtManager {
++public:
++ typedef ConnectionManager::port_type port_type;
++
++ struct statistics_type {
++ // Cycle; 0=inactive, 1=initial bootstrapping, 2 and up=normal operation
++ unsigned int cycle;
++
++ // UDP transfer rates.
++ const Rate& up_rate;
++ const Rate& down_rate;
++
++ // DHT query statistics.
++ unsigned int queries_received;
++ unsigned int queries_sent;
++ unsigned int replies_received;
++
++ // DHT node info.
++ unsigned int num_nodes;
++ unsigned int num_buckets;
++
++ // DHT tracker info.
++ unsigned int num_peers;
++ unsigned int max_peers;
++ unsigned int num_trackers;
++
++ statistics_type(const Rate& up, const Rate& down) : up_rate(up),
down_rate(down) { }
++ };
++
++ DhtManager() : m_router(NULL), m_portSent(0), m_canReceive(true) { };
++ ~DhtManager();
++
++ void initialize(const Object& dhtCache);
++
++ void start(port_type port);
++ void stop();
++
++ // Store DHT cache in the given container and return the container.
++ Object* store_cache(Object* container) const;
++
++ bool is_valid() const { return
m_router; }
++ bool is_active() const;
++
++ port_type port() const { return
m_port; }
++
++ bool can_receive_queries() const { return
m_canReceive; }
++
++ // Call this after sending the port to a client, so the router knows
++ // that it should be getting requests.
++ void port_sent() {
m_portSent++; }
++
++ // Add a node by address, e.g. from a torrent file (external = true), or
from
++ // explicit add_node commands or the BT PORT message (external = false).
++ void add_node(const sockaddr* addr, int port, bool
external);
++
++ statistics_type get_statistics() const;
++ void reset_statistics();
++
++ // To be called if upon examining the statistics, the client decides that
++ // we can't receive outside requests and therefore shouldn't advertise our
++ // UDP port after the BT handshake.
++ void set_can_receive(bool can) {
m_canReceive = can; }
++
++ // Internal libTorrent use only
++ DhtRouter* router() { return
m_router; }
++
++private:
++ DhtRouter* m_router;
++ port_type m_port;
++
++ int m_portSent;
++ bool m_canReceive;
++};
++
++}
++
++#endif
+Index: libtorrent/src/torrent/hash_string.h
+===================================================================
+--- libtorrent/src/torrent/hash_string.h (revision 975)
++++ libtorrent/src/torrent/hash_string.h (working copy)
+@@ -83,6 +83,10 @@
+
+ const value_type* c_str() const { return m_data; }
+
++ std::string str() const { return
std::string(m_data, size_data); }
++
++ void clear(int v = 0) {
std::memset(data(), v, size()); }
++
+ void assign(const value_type* src) {
std::memcpy(data(), src, size()); }
+
+ bool equal_to(const char* hash) const { return
std::memcmp(m_data, hash, size()) == 0; }
+@@ -93,6 +97,8 @@
+ static const HashString* cast_from(const char* src) { return
(const HashString*)src; }
+ static const HashString* cast_from(const std::string& src) { return
(const HashString*)src.c_str(); }
+
++ static HashString* cast_from(char* src) { return
(HashString*)src; }
++
+ private:
+ char m_data[size_data];
+ };
+@@ -103,10 +109,20 @@
+ }
+
+ inline bool
++operator != (const HashString& one, const HashString& two) {
++ return std::memcmp(one.begin(), two.begin(), HashString::size_data) != 0;
++}
++
++inline bool
+ operator < (const HashString& one, const HashString& two) {
+ return std::memcmp(one.begin(), two.begin(), HashString::size_data) < 0;
+ }
+
++inline bool
++operator <= (const HashString& one, const HashString& two) {
++ return std::memcmp(one.begin(), two.begin(), HashString::size_data) <= 0;
+ }
+
++}
++
+ #endif
+Index: libtorrent/src/torrent/Makefile.am
+===================================================================
+--- libtorrent/src/torrent/Makefile.am (revision 975)
++++ libtorrent/src/torrent/Makefile.am (working copy)
+@@ -12,6 +12,8 @@
+ common.h \
+ connection_manager.cc \
+ connection_manager.h \
++ dht_manager.cc \
++ dht_manager.h \
+ download.cc \
+ download.h \
+ error.cc \
+@@ -54,6 +56,7 @@
+ chunk_manager.h \
+ common.h \
+ connection_manager.h \
++ dht_manager.h \
+ download.h \
+ error.h \
+ exceptions.h \
+Index: libtorrent/src/torrent/common.h
+===================================================================
+--- libtorrent/src/torrent/common.h (revision 975)
++++ libtorrent/src/torrent/common.h (working copy)
+@@ -67,6 +67,8 @@
+ class ClientList;
+ class ConnectionList;
+ class ConnectionManager;
++class DhtManager;
++class DhtRouter;
+ class Download;
+ class DownloadMain;
+ class DownloadWrapper;
+Index: libtorrent/src/manager.cc
+===================================================================
+--- libtorrent/src/manager.cc (revision 975)
++++ libtorrent/src/manager.cc (working copy)
+@@ -50,6 +50,7 @@
+
+ #include "torrent/chunk_manager.h"
+ #include "torrent/connection_manager.h"
++#include "torrent/dht_manager.h"
+ #include "torrent/data/file_manager.h"
+ #include "torrent/peer/client_list.h"
+
+@@ -70,6 +71,7 @@
+ m_chunkManager(new ChunkManager),
+ m_clientList(new ClientList),
+ m_connectionManager(new ConnectionManager),
++ m_dhtManager(new DhtManager),
+
+ m_poll(NULL),
+
+@@ -99,6 +101,7 @@
+ delete m_hashQueue;
+
+ delete m_resourceManager;
++ delete m_dhtManager;
+ delete m_connectionManager;
+ delete m_chunkManager;
+
+Index: libtorrent/src/manager.h
+===================================================================
+--- libtorrent/src/manager.h (revision 975)
++++ libtorrent/src/manager.h (working copy)
+@@ -58,6 +58,7 @@
+ class ChunkManager;
+ class ConnectionManager;
+ class ThrottleManager;
++class DhtManager;
+
+ typedef std::list<std::string> EncodingList;
+
+@@ -75,6 +76,7 @@
+ ChunkManager* chunk_manager() { return
m_chunkManager; }
+ ClientList* client_list() { return
m_clientList; }
+ ConnectionManager* connection_manager() { return
m_connectionManager; }
++ DhtManager* dht_manager() { return
m_dhtManager; }
+
+ Poll* poll() { return
m_poll; }
+ void set_poll(Poll* p) { m_poll =
p; }
+@@ -99,6 +101,7 @@
+ ChunkManager* m_chunkManager;
+ ClientList* m_clientList;
+ ConnectionManager* m_connectionManager;
++ DhtManager* m_dhtManager;
+ Poll* m_poll;
+
+ EncodingList m_encodingList;
+Index: libtorrent/src/protocol/protocol_base.h
+===================================================================
+--- libtorrent/src/protocol/protocol_base.h (revision 975)
++++ libtorrent/src/protocol/protocol_base.h (working copy)
+@@ -62,6 +62,7 @@
+ REQUEST,
+ PIECE,
+ CANCEL,
++ PORT,
+
+ EXTENSION_PROTOCOL = 20,
+
+@@ -108,6 +109,7 @@
+ void write_request(const Piece& p);
+ void write_cancel(const Piece& p);
+ void write_piece(const Piece& p);
++ void write_port(uint16_t port);
+ void write_extension(uint8_t id, uint32_t length);
+
+ static const size_type sizeof_keepalive = 4;
+@@ -122,6 +124,8 @@
+ static const size_type sizeof_cancel_body = 12;
+ static const size_type sizeof_piece = 13;
+ static const size_type sizeof_piece_body = 8;
++ static const size_type sizeof_port = 7;
++ static const size_type sizeof_port_body = 2;
+ static const size_type sizeof_extension = 6;
+ static const size_type sizeof_extension_body=1;
+
+@@ -133,12 +137,14 @@
+ bool can_write_request() const { return
m_buffer.reserved_left() >= sizeof_request; }
+ bool can_write_cancel() const { return
m_buffer.reserved_left() >= sizeof_cancel; }
+ bool can_write_piece() const { return
m_buffer.reserved_left() >= sizeof_piece; }
++ bool can_write_port() const { return
m_buffer.reserved_left() >= sizeof_port; }
+ bool can_write_extension() const { return
m_buffer.reserved_left() >= sizeof_extension; }
+
+ bool can_read_have_body() const { return
m_buffer.remaining() >= sizeof_have_body; }
+ bool can_read_request_body() const { return
m_buffer.remaining() >= sizeof_request_body; }
+ bool can_read_cancel_body() const { return
m_buffer.remaining() >= sizeof_request_body; }
+ bool can_read_piece_body() const { return
m_buffer.remaining() >= sizeof_piece_body; }
++ bool can_read_port_body() const { return
m_buffer.remaining() >= sizeof_port_body; }
+ bool can_read_extension_body() const { return
m_buffer.remaining() >= sizeof_extension_body; }
+
+ protected:
+@@ -223,6 +229,13 @@
+ }
+
+ inline void
++ProtocolBase::write_port(uint16_t port) {
++ m_buffer.write_32(3);
++ write_command(PORT);
++ m_buffer.write_16(port);
++}
++
++inline void
+ ProtocolBase::write_extension(uint8_t id, uint32_t length) {
+ m_buffer.write_32(2 + length);
+ write_command(EXTENSION_PROTOCOL);
+Index: libtorrent/src/protocol/peer_connection_base.cc
+===================================================================
+--- libtorrent/src/protocol/peer_connection_base.cc (revision 975)
++++ libtorrent/src/protocol/peer_connection_base.cc (working copy)
+@@ -51,6 +51,7 @@
+ #include "download/download_main.h"
+ #include "net/socket_base.h"
+ #include "torrent/connection_manager.h"
++#include "torrent/dht_manager.h"
+ #include "torrent/peer/peer_info.h"
+
+ #include "extensions.h"
+@@ -118,10 +119,10 @@
+ m_peerChunks.bitfield()->swap(*bitfield);
+
+
m_peerChunks.upload_throttle()->set_list_iterator(m_download->upload_throttle()->end());
+- m_peerChunks.upload_throttle()->slot_activate(rak::make_mem_fun(this,
&PeerConnectionBase::receive_throttle_up_activate));
++
m_peerChunks.upload_throttle()->slot_activate(rak::make_mem_fun(static_cast<SocketBase*>(this),
&SocketBase::receive_throttle_up_activate));
+
+
m_peerChunks.download_throttle()->set_list_iterator(m_download->download_throttle()->end());
+- m_peerChunks.download_throttle()->slot_activate(rak::make_mem_fun(this,
&PeerConnectionBase::receive_throttle_down_activate));
++
m_peerChunks.download_throttle()->slot_activate(rak::make_mem_fun(static_cast<SocketBase*>(this),
&SocketBase::receive_throttle_down_activate));
+
+ download_queue()->set_delegator(m_download->delegator());
+ download_queue()->set_peer_chunks(&m_peerChunks);
+@@ -323,16 +324,6 @@
+ }
+
+ void
+-PeerConnectionBase::receive_throttle_down_activate() {
+- manager->poll()->insert_read(this);
+-}
+-
+-void
+-PeerConnectionBase::receive_throttle_up_activate() {
+- manager->poll()->insert_write(this);
+-}
+-
+-void
+ PeerConnectionBase::event_error() {
+ m_download->connection_list()->erase(this, 0);
+ }
+Index: libtorrent/src/protocol/peer_connection_base.h
+===================================================================
+--- libtorrent/src/protocol/peer_connection_base.h (revision 975)
++++ libtorrent/src/protocol/peer_connection_base.h (working copy)
+@@ -140,9 +140,6 @@
+
+ void load_up_chunk();
+
+- void receive_throttle_down_activate();
+- void receive_throttle_up_activate();
+-
+ void read_request_piece(const Piece& p);
+ void read_cancel_piece(const Piece& p);
+
+Index: libtorrent/src/protocol/peer_connection_leech.cc
+===================================================================
+--- libtorrent/src/protocol/peer_connection_leech.cc (revision 975)
++++ libtorrent/src/protocol/peer_connection_leech.cc (working copy)
+@@ -45,6 +45,8 @@
+ #include "download/chunk_statistics.h"
+ #include "download/download_info.h"
+ #include "download/download_main.h"
++#include "torrent/dht_manager.h"
++#include "torrent/peer/peer_info.h"
+
+ #include "extensions.h"
+ #include "peer_connection_leech.h"
+@@ -275,6 +277,13 @@
+ read_cancel_piece(m_down->read_request());
+ return true;
+
++ case ProtocolBase::PORT:
++ if (!m_down->can_read_port_body())
++ break;
++
++ manager->dht_manager()->add_node(m_peerInfo->socket_address(),
m_down->buffer()->read_16(), false);
++ return true;
++
+ case ProtocolBase::EXTENSION_PROTOCOL:
+ if (!m_down->can_read_extension_body())
+ break;
+Index: libtorrent/src/protocol/peer_connection_seed.cc
+===================================================================
+--- libtorrent/src/protocol/peer_connection_seed.cc (revision 975)
++++ libtorrent/src/protocol/peer_connection_seed.cc (working copy)
+@@ -43,6 +43,8 @@
+ #include "download/chunk_statistics.h"
+ #include "download/download_info.h"
+ #include "download/download_main.h"
++#include "torrent/dht_manager.h"
++#include "torrent/peer/peer_info.h"
+
+ #include "extensions.h"
+ #include "peer_connection_seed.h"
+@@ -175,6 +177,13 @@
+ read_cancel_piece(m_down->read_request());
+ return true;
+
++ case ProtocolBase::PORT:
++ if (!m_down->can_read_port_body())
++ break;
++
++ manager->dht_manager()->add_node(m_peerInfo->socket_address(),
m_down->buffer()->read_16(), false);
++ return true;
++
+ case ProtocolBase::EXTENSION_PROTOCOL:
+ if (!m_down->can_read_extension_body())
+ break;
+Index: libtorrent/src/protocol/handshake.cc
+===================================================================
+--- libtorrent/src/protocol/handshake.cc (revision 975)
++++ libtorrent/src/protocol/handshake.cc (working copy)
+@@ -38,6 +38,7 @@
+
+ #include "download/download_info.h"
+ #include "download/download_main.h"
++#include "torrent/dht_manager.h"
+ #include "net/throttle_list.h"
+ #include "net/throttle_manager.h"
+ #include "torrent/exceptions.h"
+@@ -522,18 +523,19 @@
+
+ // The download is just starting so we're not sending any
+ // bitfield.
+- if (m_download->file_list()->bitfield()->is_all_unset())
+- prepare_keepalive();
+- else
++ if (m_download->file_list()->bitfield()->is_all_unset()) {
++ prepare_post_handshake(true);
++
++ } else {
+ prepare_bitfield();
+
+- m_state = BITFIELD;
++ // Give some extra time for reading/writing the bitfield.
++ priority_queue_erase(&taskScheduler, &m_taskTimeout);
++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(120)).round_seconds());
++ }
++
+ manager->poll()->insert_write(this);
+
+- // Give some extra time for reading/writing the bitfield.
+- priority_queue_erase(&taskScheduler, &m_taskTimeout);
+- priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime +
rak::timer::from_seconds(120)).round_seconds());
+-
+ return false;
+ }
+
+@@ -731,6 +733,7 @@
+ goto restart;
+
+ case BITFIELD:
++ case POST_HANDSHAKE:
+ read_bitfield();
+ break;
+
+@@ -864,8 +867,12 @@
+ if (!m_writeBuffer.remaining())
+ throw internal_error("event_write called with empty write buffer.");
+
+- if (m_writeBuffer.consume(write_unthrottled(m_writeBuffer.position(),
m_writeBuffer.remaining())))
+- manager->poll()->remove_write(this);
++ if (m_writeBuffer.consume(write_stream_throws(m_writeBuffer.position(),
m_writeBuffer.remaining()))) {
++ if (m_state == POST_HANDSHAKE)
++ write_done();
++ else
++ manager->poll()->remove_write(this);
++ }
+
+ } catch (handshake_succeeded& e) {
+ m_manager->receive_succeeded(this);
+@@ -948,6 +955,8 @@
+ m_writeBuffer.write_range(m_protocol, m_protocol + 19);
+
+ std::memset(m_writeBuffer.end(), 0, 8);
++ if (manager->dht_manager()->is_active())
++ *(m_writeBuffer.end()+7) |= 0x01; // DHT support, enable PORT message
+ m_writeBuffer.move_end(8);
+
+ m_writeBuffer.write_range(m_download->info()->hash().c_str(),
m_download->info()->hash().c_str() + 20);
+@@ -990,20 +999,50 @@
+ m_encryption.info()->encrypt(m_writeBuffer.end() - 5, 5);
+
+ m_writePos = 0;
++
++ m_state = BITFIELD;
+ }
+
+ void
+-Handshake::prepare_keepalive() {
+- m_writeBuffer.write_32(0);
++Handshake::prepare_post_handshake(bool must_write) {
++ m_state = POST_HANDSHAKE;
+
+- if (m_encryption.info()->is_encrypted())
+- m_encryption.info()->encrypt(m_writeBuffer.end() - 4, 4);
++ // Send PORT message for DHT if enabled and peer supports it.
++ if (m_peerInfo->supports_dht() && manager->dht_manager()->is_active() &&
manager->dht_manager()->can_receive_queries()) {
++ m_writeBuffer.write_32(3);
++ m_writeBuffer.write_8(protocol_port);
++ m_writeBuffer.write_16(manager->dht_manager()->port());
++ manager->dht_manager()->port_sent();
++ if (m_encryption.info()->is_encrypted())
++ m_encryption.info()->encrypt(m_writeBuffer.end() - 7, 7);
+
+- // Skip writting the bitfield.
+- m_writePos = m_download->file_list()->bitfield()->size_bytes();
++ must_write = false;
++ }
++
++ // Send a keep-alive if we still must send something.
++ if (must_write) {
++ m_writeBuffer.write_32(0);
++
++ if (m_encryption.info()->is_encrypted())
++ m_encryption.info()->encrypt(m_writeBuffer.end() - 4, 4);
++ }
++
++ if (!m_writeBuffer.remaining())
++ write_done();
+ }
+
+ void
++Handshake::write_done() {
++ m_writeDone = true;
++ manager->poll()->remove_write(this);
++
++ // Ok to just check m_readDone as the call in event_read() won't
++ // set it before the call.
++ if (m_readDone)
++ throw handshake_succeeded();
++}
++
++void
+ Handshake::write_extension_handshake() {
+ if (m_extensions->is_default())
+ m_extensions = new ProtocolExtension;
+@@ -1059,15 +1098,8 @@
+ }
+ }
+
+- if (m_writePos == bitfield->size_bytes()) {
+- m_writeDone = true;
+- manager->poll()->remove_write(this);
+-
+- // Ok to just check m_readDone as the call in event_read() won't
+- // set it before the call.
+- if (m_readDone)
+- throw handshake_succeeded();
+- }
++ if (m_writePos == bitfield->size_bytes())
++ prepare_post_handshake(false);
+ }
+
+ void
+Index: libtorrent/src/protocol/handshake.h
+===================================================================
+--- libtorrent/src/protocol/handshake.h (revision 975)
++++ libtorrent/src/protocol/handshake.h (working copy)
+@@ -74,6 +74,7 @@
+ INACTIVE,
+ CONNECTING,
+ BITFIELD,
++ POST_HANDSHAKE,
+
+ PROXY_CONNECT,
+ PROXY_DONE,
+@@ -130,6 +131,7 @@
+ void operator = (const Handshake&);
+
+ void read_done();
++ void write_done();
+
+ bool fill_read_buffer(int size);
+
+@@ -151,7 +153,7 @@
+ void prepare_handshake();
+ void prepare_peer_info();
+ void prepare_bitfield();
+- void prepare_keepalive();
++ void prepare_post_handshake(bool must_write);
+
+ void write_extension_handshake();
+ void write_bitfield();
+Index: libtorrent/src/download/download_constructor.cc
+===================================================================
+--- libtorrent/src/download/download_constructor.cc (revision 975)
++++ libtorrent/src/download/download_constructor.cc (working copy)
+@@ -42,6 +42,7 @@
+ #include <rak/string_manip.h>
+
+ #include "download/download_wrapper.h"
++#include "torrent/dht_manager.h"
+ #include "torrent/exceptions.h"
+ #include "torrent/object.h"
+ #include "torrent/data/file.h"
+@@ -50,6 +51,8 @@
+
+ #include "download_constructor.h"
+
++#include "manager.h"
++
+ namespace torrent {
+
+ struct download_constructor_is_single_path {
+@@ -167,9 +170,16 @@
+ else if (b.has_key("announce"))
+ add_tracker_single(b.get_key("announce"), 0);
+
+- else
++ else if (!manager->dht_manager()->is_valid() ||
m_download->info()->is_private())
+ throw bencode_error("Could not find any trackers");
+
++ if (manager->dht_manager()->is_valid() &&
!m_download->info()->is_private())
++ tracker->insert(tracker->group_size(), "dht://");
++
++ if (manager->dht_manager()->is_valid() && b.has_key_list("nodes"))
++ std::for_each(b.get_key_list("nodes").begin(),
b.get_key_list("nodes").end(),
++ rak::make_mem_fun(this,
&DownloadConstructor::add_dht_node));
++
+ tracker->randomize();
+ }
+
+@@ -191,6 +201,35 @@
+ m_download->main()->tracker_manager()->insert(group,
rak::trim_classic(b.as_string()));
+ }
+
++struct call_add_node_t {
++ call_add_node_t(int port) : m_port(port) { }
++
++ void operator() (const sockaddr* sa, int err) {
++ if (sa != NULL)
++ manager->dht_manager()->add_node(sa, m_port, true);
++ }
++
++ int m_port;
++};
++
++void
++DownloadConstructor::add_dht_node(const Object& b) {
++ if (!b.is_list() || b.as_list().size() < 2)
++ return;
++
++ Object::list_type::const_iterator el = b.as_list().begin();
++
++ if (!el->is_string())
++ return;
++
++ const std::string& host = el->as_string();
++
++ if (!(++el)->is_value())
++ return;
++
++ manager->connection_manager()->resolver()(host.c_str(),
(int)rak::socket_address::pf_inet, SOCK_DGRAM,
call_add_node_t(el->as_value()));
++}
++
+ bool
+ DownloadConstructor::is_valid_path_element(const Object& b) {
+ return
+Index: libtorrent/src/download/download_constructor.h
+===================================================================
+--- libtorrent/src/download/download_constructor.h (revision 975)
++++ libtorrent/src/download/download_constructor.h (working copy)
+@@ -67,6 +67,7 @@
+
+ void add_tracker_group(const Object& b);
+ void add_tracker_single(const Object& b, int group);
++ void add_dht_node(const Object& b);
+
+ static bool is_valid_path_element(const Object& b);
+ static bool is_invalid_path_element(const Object& b) { return
!is_valid_path_element(b); }
+Index: libtorrent/src/Makefile.am
+===================================================================
+--- libtorrent/src/Makefile.am (revision 975)
++++ libtorrent/src/Makefile.am (working copy)
+@@ -1,6 +1,7 @@
+ SUBDIRS = \
+ torrent \
+ data \
++ dht \
+ download \
+ net \
+ protocol \
+@@ -15,6 +16,7 @@
+ $(top_srcdir)/src/torrent/data/libsub_torrentdata.la \
+ $(top_srcdir)/src/torrent/peer/libsub_torrentpeer.la \
+ $(top_srcdir)/src/data/libsub_data.la \
++ $(top_srcdir)/src/dht/libsub_dht.la \
+ $(top_srcdir)/src/download/libsub_download.la \
+ $(top_srcdir)/src/net/libsub_net.la \
+ $(top_srcdir)/src/protocol/libsub_protocol.la \
+Index: libtorrent/configure.ac
+===================================================================
+--- libtorrent/configure.ac (revision 975)
++++ libtorrent/configure.ac (working copy)
+@@ -2,6 +2,7 @@
+
+ dnl Find a better way to do this
+ AC_DEFINE(PEER_NAME, "-lt0B80-", Identifier that is part of the default
peer id)
++AC_DEFINE(PEER_VERSION, "lt\x0B\x80", 4 byte client and version identifier
for DHT)
+
+ LIBTORRENT_CURRENT=10
+ LIBTORRENT_REVISION=8
+@@ -32,6 +33,7 @@
+ TORRENT_ENABLE_DEBUG
+ TORRENT_ENABLE_EXTRA_DEBUG
+ TORRENT_ENABLE_WERROR
++TORRENT_ENABLE_TR1
+
+ TORRENT_DISABLE_IPV6
+
+@@ -104,6 +106,7 @@
+ src/torrent/peer/Makefile
+ src/torrent/data/Makefile
+ src/data/Makefile
++ src/dht/Makefile
+ src/download/Makefile
+ src/net/Makefile
+ src/protocol/Makefile
diff --git a/ftp/rtorrent/CONFIGURE b/ftp/rtorrent/CONFIGURE
new file mode 100755
index 0000000..9543302
--- /dev/null
+++ b/ftp/rtorrent/CONFIGURE
@@ -0,0 +1,2 @@
+config_query DHT_PATCH "Patch to support dht?" y
+
diff --git a/ftp/rtorrent/DEPENDS b/ftp/rtorrent/DEPENDS
index d4eb638..720b3ea 100755
--- a/ftp/rtorrent/DEPENDS
+++ b/ftp/rtorrent/DEPENDS
@@ -1,2 +1,6 @@
-depends libtorrent &&
+if [[ ${DHT_PATCH} != n ]]; then
+ depends -sub DHT libtorrent
+else
+ depends libtorrent
+fi &&
depends curl
diff --git a/ftp/rtorrent/HISTORY b/ftp/rtorrent/HISTORY
index 5004d00..02c24dc 100644
--- a/ftp/rtorrent/HISTORY
+++ b/ftp/rtorrent/HISTORY
@@ -1,3 +1,6 @@
+2007-10-03 Mathieu Lonjaret <lejatorn AT sourcemage.org>
+ * DEPENDS, CONFIGURE, PRE_BUILD, dht.diff: added support for dht
+
2007-09-10 Mathieu Lonjaret <lejatorn AT sourcemage.org>
* DETAILS: version update to 0.7.8

diff --git a/ftp/rtorrent/PRE_BUILD b/ftp/rtorrent/PRE_BUILD
new file mode 100755
index 0000000..ec4e6ac
--- /dev/null
+++ b/ftp/rtorrent/PRE_BUILD
@@ -0,0 +1,7 @@
+default_pre_build &&
+cd ${SOURCE_DIRECTORY} &&
+if [[ ${DHT_PATCH} != n ]]; then
+ cp ${SPELL_DIRECTORY}/dht.diff ${SOURCE_DIRECTORY}/dht.diff &&
+ patch -p1 < dht.diff
+fi
+
diff --git a/ftp/rtorrent/dht.diff b/ftp/rtorrent/dht.diff
new file mode 100644
index 0000000..e74c528
--- /dev/null
+++ b/ftp/rtorrent/dht.diff
@@ -0,0 +1,707 @@
+Index: rtorrent/doc/rtorrent.rc
+===================================================================
+--- rtorrent/doc/rtorrent.rc (revision 975)
++++ rtorrent/doc/rtorrent.rc (working copy)
+@@ -74,6 +74,17 @@
+ #
+ # encryption = allow_incoming,enable_retry,prefer_plaintext
+
++# Enable DHT support for trackerless torrents or when all trackers are down.
++# May be set to "disable" (completely disable DHT), "off" (do not start
DHT),
++# "auto" (start and stop DHT as needed), or "on" (start DHT immediately).
++# The default is "off". For DHT to work, a session directory must be
defined.
++#
++# dht = auto
++
++# UDP port to use for DHT.
++#
++# dht_port = 6881
++
+ # Enable peer exchange (for torrents not marked private)
+ #
+ # peer_exchange = yes
+Index: rtorrent/doc/rtorrent.1.xml
+===================================================================
+--- rtorrent/doc/rtorrent.1.xml (revision 975)
++++ rtorrent/doc/rtorrent.1.xml (working copy)
+@@ -1,4 +1,5 @@
+-<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
++<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN"
++ "http://oasis-open.org/docbook/xml/4.1/docbookx.dtd";>
+
+ <refentry id="rtorrent">
+
+@@ -705,7 +706,39 @@
+ </para></listitem>
+ </varlistentry>
+
++ <varlistentry>
++ <term>dht = <replaceable>disabled|off|auto|on</replaceable></term>
++ <listitem><para>
++Support for querying the distributed hash table (DHT) to find peers for
trackerless
++torrents or when all trackers are down. Set to <emphasis>disable</emphasis>
to completely
++disable DHT, <emphasis>off</emphasis> (default) to enable DHT but to not
start the
++DHT server, <emphasis>auto</emphasis> to automatically start and stop the
DHT server
++as needed or <emphasis>on</emphasis> for permanently keeping the DHT server
running.
++When set to automatic, the DHT server will start up when the first
non-private torrent
++is started, and will stop 15-30 minutes after the last non-private torrent
is
++stopped (or when rTorrent quits). For DHT to work, a session directory must
be set (for
++saving the DHT cache).
++ </para></listitem>
++ </varlistentry>
++
+ <varlistentry>
++ <term>dht_port = <replaceable>number</replaceable></term>
++ <listitem><para>
++Set the UDP listen port for DHT. Defaults to 6881.
++ </para></listitem>
++ </varlistentry>
++
++ <varlistentry>
++ <term>dht_add_node = <replaceable>host[:port]</replaceable></term>
++ <listitem><para>
++Not intended for use in the configuration file but as one-time option in the
++client or on the command line to bootstrap an empty DHT node table.
Contacts
++the given node and attempts to bootstrap from it if it replies.
++The port is optional, with port 6881 being used by default.
++ </para></listitem>
++ </varlistentry>
++
++ <varlistentry>
+ <term>http_capath = <replaceable>path</replaceable></term>
+ <term>http_cacert = <replaceable>filename</replaceable></term>
+ <listitem><para>
+Index: rtorrent/src/core/download_list.cc
+===================================================================
+--- rtorrent/src/core/download_list.cc (revision 975)
++++ rtorrent/src/core/download_list.cc (working copy)
+@@ -55,6 +55,7 @@
+ #include "globals.h"
+ #include "manager.h"
+
++#include "dht_manager.h"
+ #include "download.h"
+ #include "download_list.h"
+ #include "download_store.h"
+@@ -94,6 +95,8 @@
+ void
+ DownloadList::session_save() {
+ std::for_each(begin(), end(),
std::bind1st(std::mem_fun(&DownloadStore::save),
control->core()->download_store()));
++
++ control->dht_manager()->save_dht_cache();
+ }
+
+ DownloadList::iterator
+@@ -388,6 +391,9 @@
+ torrent::resume_save_progress(*download->download(),
download->download()->bencode()->get_key("libtorrent_resume"), true);
+ }
+
++ // If the DHT server is set to auto, start it now.
++ control->dht_manager()->auto_start();
++
+ // Update the priority to ensure it has the correct
+ // seeding/unfinished modifiers.
+ download->set_priority(download->priority());
+Index: rtorrent/src/core/dht_manager.cc
+===================================================================
+--- rtorrent/src/core/dht_manager.cc (revision 0)
++++ rtorrent/src/core/dht_manager.cc (revision 0)
+@@ -0,0 +1,302 @@
++// rTorrent - BitTorrent client
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#include "config.h"
++
++#include <fstream>
++#include <sstream>
++#include <torrent/object.h>
++#include <torrent/dht_manager.h>
++#include <torrent/object_stream.h>
++#include <torrent/rate.h>
++
++#include "rpc/parse_commands.h"
++
++#include "globals.h"
++
++#include "control.h"
++#include "dht_manager.h"
++#include "download.h"
++#include "download_store.h"
++#include "manager.h"
++
++namespace core {
++
++const char* DhtManager::dht_settings[dht_settings_num] = { "disable",
"off", "auto", "on" };
++
++DhtManager::~DhtManager() {
++ priority_queue_erase(&taskScheduler, &m_updateTimeout);
++ priority_queue_erase(&taskScheduler, &m_stopTimeout);
++}
++
++void
++DhtManager::load_dht_cache() {
++ if (m_start == dht_disable ||
!control->core()->download_store()->is_enabled())
++ return;
++
++ torrent::Object cache(torrent::Object::TYPE_MAP);
++ std::fstream cache_file((control->core()->download_store()->path() +
"rtorrent.dht_cache").c_str(), std::ios::in | std::ios::binary);
++
++ if (cache_file.is_open()) {
++ cache_file >> cache;
++
++ if (cache_file.fail())
++ throw torrent::input_error("Invalid DHT cache.");
++ }
++
++ try {
++ torrent::dht_manager()->initialize(cache);
++
++ if (m_start == dht_on)
++ start_dht();
++
++ } catch (torrent::local_error& e) {
++ control->core()->push_log((std::string("DHT error: ") +
e.what()).c_str());
++ }
++}
++
++void
++DhtManager::start_dht() {
++ priority_queue_erase(&taskScheduler, &m_stopTimeout);
++
++ if (torrent::dht_manager()->is_active() ||
!torrent::dht_manager()->is_valid())
++ return;
++
++ int port = rpc::call_command_value("get_dht_port");
++ if (port <= 0)
++ return;
++
++ char msg[128];
++ snprintf(msg, sizeof(msg), "Starting DHT server on port %d.", port);
++ control->core()->push_log(msg);
++
++ try {
++ torrent::dht_manager()->start(port);
++
++ m_updateTimeout.set_slot(rak::mem_fn(this, &DhtManager::update));
++ priority_queue_insert(&taskScheduler, &m_updateTimeout, (cachedTime +
rak::timer::from_seconds(60)).round_seconds());
++
++ m_dhtPrevCycle = 0;
++ m_dhtPrevQueriesSent = 0;
++ m_dhtPrevRepliesReceived = 0;
++ m_dhtPrevQueriesReceived = 0;
++ m_dhtPrevBytesUp = 0;
++ m_dhtPrevBytesDown = 0;
++
++ } catch (torrent::local_error& e) {
++ control->core()->push_log((std::string("DHT error: ") +
e.what()).c_str());
++ m_start = dht_off;
++ }
++}
++
++void
++DhtManager::stop_dht() {
++ priority_queue_erase(&taskScheduler, &m_updateTimeout);
++ priority_queue_erase(&taskScheduler, &m_stopTimeout);
++ if (m_stopTimeout.is_queued())
++ priority_queue_erase(&taskScheduler, &m_stopTimeout);
++
++ if (torrent::dht_manager()->is_active()) {
++ log_statistics(true);
++ control->core()->push_log("Stopping DHT server.");
++ torrent::dht_manager()->stop();
++ }
++}
++
++void
++DhtManager::save_dht_cache() {
++ if (!control->core()->download_store()->is_enabled() ||
!torrent::dht_manager()->is_active())
++ return;
++
++ std::string filename = control->core()->download_store()->path() +
"rtorrent.dht_cache";
++ std::string filename_tmp = filename + ".new";
++ std::fstream cache_file(filename_tmp.c_str(), std::ios::out |
std::ios::trunc);
++
++ if (!cache_file.is_open())
++ return;
++
++ torrent::Object cache(torrent::Object::TYPE_MAP);
++ cache_file << *torrent::dht_manager()->store_cache(&cache);
++
++ if (!cache_file.good())
++ return;
++
++ cache_file.close();
++
++ ::rename(filename_tmp.c_str(), filename.c_str());
++}
++
++void
++DhtManager::set_start(const std::string& arg) {
++ int i;
++ for (i = 0; i < dht_settings_num; i++) {
++ if (arg == dht_settings[i]) {
++ m_start = i;
++ break;
++ }
++ }
++
++ if (i == dht_settings_num)
++ throw torrent::input_error("Invalid argument.");
++
++ if (m_start == dht_off)
++ stop_dht();
++ else if (m_start == dht_on)
++ start_dht();
++}
++
++void
++DhtManager::update() {
++ if (!torrent::dht_manager()->is_active())
++ throw torrent::internal_error("DhtManager::update called with DHT
inactive.");
++
++ if (m_start == dht_auto && !m_stopTimeout.is_queued()) {
++ DownloadList::const_iterator itr =
std::find_if(control->core()->download_list()->begin(),
control->core()->download_list()->end(), std::mem_fun(&Download::is_active));
++ if (itr == control->core()->download_list()->end()) {
++ m_stopTimeout.set_slot(rak::mem_fn(this, &DhtManager::stop_dht));
++ priority_queue_insert(&taskScheduler, &m_stopTimeout, (cachedTime +
rak::timer::from_seconds(15 * 60)).round_seconds());
++ }
++ }
++
++ // While bootstrapping (log_statistics returns true), check every minute
if it completed, otherwise update every 15 minutes.
++ if (log_statistics(false))
++ priority_queue_insert(&taskScheduler, &m_updateTimeout, (cachedTime +
rak::timer::from_seconds(60)).round_seconds());
++ else
++ priority_queue_insert(&taskScheduler, &m_updateTimeout, (cachedTime +
rak::timer::from_seconds(15 * 60)).round_seconds());
++}
++
++bool
++DhtManager::log_statistics(bool force) {
++ torrent::DhtManager::statistics_type stats =
torrent::dht_manager()->get_statistics();
++
++ // Check for firewall problems.
++
++ if (stats.cycle > 2 && stats.queries_sent - m_dhtPrevQueriesSent > 100 &&
stats.queries_received == m_dhtPrevQueriesReceived) {
++ // We should have had clients ping us at least but have received
++ // nothing, that means the UDP port is probably unreachable.
++ if (torrent::dht_manager()->can_receive_queries())
++ control->core()->push_log("Warning: DHT port appears to be
unreachable, no queries received.");
++
++ torrent::dht_manager()->set_can_receive(false);
++ }
++
++ if (stats.queries_sent - m_dhtPrevQueriesSent > stats.num_nodes * 2 + 20
&& stats.replies_received == m_dhtPrevRepliesReceived) {
++ // No replies to over 20 queries plus two per node we have. Probably
firewalled.
++ if (!m_warned)
++ control->core()->push_log("Warning: DHT port appears to be
firewalled, no replies received.");
++
++ m_warned = true;
++ return false;
++ }
++
++ m_warned = false;
++
++ if (stats.queries_received > m_dhtPrevQueriesReceived)
++ torrent::dht_manager()->set_can_receive(true);
++
++ // Nothing to log while bootstrapping, but check again every minute.
++ if (stats.cycle <= 1) {
++ m_dhtPrevCycle = stats.cycle;
++ return true;
++ }
++
++ // If bootstrap completed between now and the previous check, notify user.
++ if (m_dhtPrevCycle == 1) {
++ char buffer[128];
++ snprintf(buffer, sizeof(buffer), "DHT bootstrap complete, have %d nodes
in %d buckets.", stats.num_nodes, stats.num_buckets);
++ control->core()->get_log_complete().push_front(buffer);
++ m_dhtPrevCycle = stats.cycle;
++ return false;
++ };
++
++ // Standard DHT statistics on first real cycle, and every 8th cycle
++ // afterwards (i.e. every 2 hours), or when forced.
++ if ((force && stats.cycle != m_dhtPrevCycle) || stats.cycle == 3 ||
stats.cycle > m_dhtPrevCycle + 7) {
++ char buffer[256];
++ snprintf(buffer, sizeof(buffer),
++ "DHT statistics: %d queries in, %d queries out, %d replies
received, %lld bytes read, %lld bytes sent, "
++ "%d known nodes in %d buckets, %d peers (highest: %d) tracked
in %d torrents.",
++ stats.queries_received - m_dhtPrevQueriesReceived,
++ stats.queries_sent - m_dhtPrevQueriesSent,
++ stats.replies_received - m_dhtPrevRepliesReceived,
++ stats.down_rate.total() - m_dhtPrevBytesDown,
++ stats.up_rate.total() - m_dhtPrevBytesUp,
++ stats.num_nodes,
++ stats.num_buckets,
++ stats.num_peers,
++ stats.max_peers,
++ stats.num_trackers);
++
++ control->core()->get_log_complete().push_front(buffer);
++
++ m_dhtPrevCycle = stats.cycle;
++ m_dhtPrevQueriesSent = stats.queries_sent;
++ m_dhtPrevRepliesReceived = stats.replies_received;
++ m_dhtPrevQueriesReceived = stats.queries_received;
++ m_dhtPrevBytesUp = stats.up_rate.total();
++ m_dhtPrevBytesDown = stats.down_rate.total();
++ }
++
++ return false;
++}
++
++torrent::Object
++DhtManager::dht_statistics() {
++ torrent::Object dhtStats(torrent::Object::TYPE_MAP);
++
++ dhtStats.insert_key("dht", dht_settings[m_start]);
++ dhtStats.insert_key("active",
torrent::dht_manager()->is_active());
++
++ if (torrent::dht_manager()->is_active()) {
++ torrent::DhtManager::statistics_type stats =
torrent::dht_manager()->get_statistics();
++
++ dhtStats.insert_key("cycle", stats.cycle);
++ dhtStats.insert_key("queries_received", stats.queries_received);
++ dhtStats.insert_key("queries_sent", stats.queries_sent);
++ dhtStats.insert_key("replies_received", stats.replies_received);
++ dhtStats.insert_key("bytes_read", stats.down_rate.total());
++ dhtStats.insert_key("bytes_written", stats.up_rate.total());
++ dhtStats.insert_key("nodes", stats.num_nodes);
++ dhtStats.insert_key("buckets", stats.num_buckets);
++ dhtStats.insert_key("peers", stats.num_peers);
++ dhtStats.insert_key("peers_max", stats.max_peers);
++ dhtStats.insert_key("torrents", stats.num_trackers);
++ }
++
++ return dhtStats;
++}
++
++}
+Index: rtorrent/src/core/dht_manager.h
+===================================================================
+--- rtorrent/src/core/dht_manager.h (revision 0)
++++ rtorrent/src/core/dht_manager.h (revision 0)
+@@ -0,0 +1,89 @@
++// rTorrent - BitTorrent client
++// Copyright (C) 2005-2007, Jari Sundell
++//
++// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
++//
++// Contact: Jari Sundell <jaris AT ifi.uio.no>
++//
++// Skomakerveien 33
++// 3185 Skoppum, NORWAY
++
++#ifndef RTORRENT_CORE_DHT_MANAGER_H
++#define RTORRENT_CORE_DHT_MANAGER_H
++
++#include <rak/priority_queue_default.h>
++
++#include <torrent/object.h>
++
++namespace core {
++
++class DhtManager {
++public:
++ DhtManager() : m_warned(false), m_start(dht_off) { }
++ ~DhtManager();
++
++ void load_dht_cache();
++ void save_dht_cache();
++ torrent::Object dht_statistics();
++
++ void start_dht();
++ void stop_dht();
++ void auto_start() { if (m_start ==
dht_auto) start_dht(); }
++
++ void set_start(const std::string& arg);
++
++private:
++ static const int dht_disable = 0;
++ static const int dht_off = 1;
++ static const int dht_auto = 2;
++ static const int dht_on = 3;
++
++ static const int dht_settings_num = 4;
++ static const char* dht_settings[dht_settings_num];
++
++ void update();
++ bool log_statistics(bool force);
++
++ unsigned int m_dhtPrevCycle;
++ unsigned int m_dhtPrevQueriesSent;
++ unsigned int m_dhtPrevRepliesReceived;
++ unsigned int m_dhtPrevQueriesReceived;
++ uint64_t m_dhtPrevBytesUp;
++ uint64_t m_dhtPrevBytesDown;
++
++ rak::priority_item m_updateTimeout;
++ rak::priority_item m_stopTimeout;
++ bool m_warned;
++
++ int m_start;
++};
++
++}
++
++#endif
+Index: rtorrent/src/core/manager.cc
+===================================================================
+--- rtorrent/src/core/manager.cc (revision 975)
++++ rtorrent/src/core/manager.cc (working copy)
+@@ -242,6 +242,9 @@
+
+ m_downloadList->clear();
+
++ // When we implement asynchronous DNS lookups, we need to cancel them
++ // here before the torrent::* objects are deleted.
++
+ torrent::cleanup();
+ CurlStack::global_cleanup();
+
+Index: rtorrent/src/core/Makefile.am
+===================================================================
+--- rtorrent/src/core/Makefile.am (revision 975)
++++ rtorrent/src/core/Makefile.am (working copy)
+@@ -5,6 +5,8 @@
+ curl_get.h \
+ curl_stack.cc \
+ curl_stack.h \
++ dht_manager.cc \
++ dht_manager.h \
+ download.cc \
+ download.h \
+ download_factory.cc \
+Index: rtorrent/src/control.cc
+===================================================================
+--- rtorrent/src/control.cc (revision 975)
++++ rtorrent/src/control.cc (working copy)
+@@ -44,6 +44,7 @@
+ #include "core/download_store.h"
+ #include "core/view_manager.h"
+ #include "core/scheduler.h"
++#include "core/dht_manager.h"
+
+ #include "display/canvas.h"
+ #include "display/window.h"
+@@ -75,6 +76,7 @@
+ m_core = new core::Manager();
+ m_viewManager = new core::ViewManager(m_core->download_list());
+ m_scheduler = new core::Scheduler(m_core->download_list());
++ m_dhtManager = new core::DhtManager();
+
+ m_inputStdin->slot_pressed(sigc::mem_fun(m_input,
&input::Manager::pressed));
+
+@@ -95,6 +97,7 @@
+ delete m_display;
+ delete m_core;
+ delete m_scheduler;
++ delete m_dhtManager;
+ }
+
+ void
+Index: rtorrent/src/display/utils.cc
+===================================================================
+--- rtorrent/src/display/utils.cc (revision 975)
++++ rtorrent/src/display/utils.cc (working copy)
+@@ -191,8 +191,8 @@
+ } else if (d->tracker_list()->is_busy() && d->tracker_list()->focus() <
d->tracker_list()->size()) {
+ torrent::TrackerList* tl = d->tracker_list();
+
+- first = print_buffer(first, last, "Tracker[%i:%i]: Connecting to %s",
+- tl->get(tl->focus()).group(), tl->focus(),
tl->get(tl->focus()).url().c_str());
++ first = print_buffer(first, last, "Tracker[%i:%i]: Connecting to %s %s",
++ tl->get(tl->focus()).group(), tl->focus(),
tl->get(tl->focus()).url().c_str(), tl->get(tl->focus()).status().c_str());
+
+ } else if (!d->message().empty()) {
+ first = print_buffer(first, last, "%s", d->message().c_str());
+Index: rtorrent/src/display/window_tracker_list.cc
+===================================================================
+--- rtorrent/src/display/window_tracker_list.cc (revision 975)
++++ rtorrent/src/display/window_tracker_list.cc (working copy)
+@@ -91,7 +91,7 @@
+ m_canvas->print(4, pos++, "Id: %s Focus: %s Enabled: %s Open: %s S/L:
%u/%u",
+ rak::copy_escape_html(tracker.tracker_id()).c_str(),
+ range.first == tl->focus() ? "yes" : " no",
+- tracker.is_enabled() ? "yes" : " no",
++ tracker.is_usable() ? "yes" : tracker.is_enabled() ?
"off" : " no",
+ tracker.is_open() ? "yes" : " no",
+ tracker.scrape_complete(),
+ tracker.scrape_incomplete());
+Index: rtorrent/src/control.h
+===================================================================
+--- rtorrent/src/control.h (revision 975)
++++ rtorrent/src/control.h (working copy)
+@@ -51,6 +51,7 @@
+ class Manager;
+ class ViewManager;
+ class Scheduler;
++ class DhtManager;
+ }
+
+ namespace display {
+@@ -89,6 +90,7 @@
+ core::Manager* core() { return m_core; }
+ core::ViewManager* view_manager() { return m_viewManager;
}
+ core::Scheduler* scheduler() { return m_scheduler; }
++ core::DhtManager* dht_manager() { return m_dhtManager; }
+
+ torrent::Poll* poll();
+
+@@ -118,6 +120,7 @@
+ core::Manager* m_core;
+ core::ViewManager* m_viewManager;
+ core::Scheduler* m_scheduler;
++ core::DhtManager* m_dhtManager;
+
+ ui::Root* m_ui;
+ display::Manager* m_display;
+Index: rtorrent/src/main.cc
+===================================================================
+--- rtorrent/src/main.cc (revision 975)
++++ rtorrent/src/main.cc (working copy)
+@@ -49,6 +49,7 @@
+ #include <execinfo.h>
+ #endif
+
++#include "core/dht_manager.h"
+ #include "core/download.h"
+ #include "core/download_factory.h"
+ #include "core/download_store.h"
+@@ -244,6 +245,7 @@
+
+ // Load session torrents and perform scheduled tasks to ensure
+ // session torrents are loaded before arg torrents.
++ control->dht_manager()->load_dht_cache();
+ load_session_torrents(control);
+ rak::priority_queue_perform(&taskScheduler, cachedTime);
+
+Index: rtorrent/src/command_network.cc
+===================================================================
+--- rtorrent/src/command_network.cc (revision 975)
++++ rtorrent/src/command_network.cc (working copy)
+@@ -41,10 +41,12 @@
+ #include <rak/file_stat.h>
+ #include <rak/path.h>
+ #include <torrent/connection_manager.h>
++#include <torrent/dht_manager.h>
+ #include <torrent/tracker.h>
+ #include <torrent/tracker_list.h>
+ #include <torrent/torrent.h>
+
++#include "core/dht_manager.h"
+ #include "core/download.h"
+ #include "core/manager.h"
+ #include "rpc/scgi.h"
+@@ -119,7 +121,42 @@
+ void apply_hash_interval(int arg) {
torrent::set_hash_interval(arg * 1000); }
+ void apply_encoding_list(const std::string& arg) {
torrent::encoding_list()->push_back(arg); }
+
++struct call_add_node_t {
++ call_add_node_t(int port) : m_port(port) { }
++
++ void operator() (const sockaddr* sa, int err) {
++ if (sa == NULL)
++ control->core()->push_log("Could not resolve host.");
++ else
++ torrent::dht_manager()->add_node(sa, m_port, false);
++ }
++
++ int m_port;
++};
++
+ void
++apply_dht_add_node(const std::string& arg) {
++ if (!torrent::dht_manager()->is_valid())
++ throw torrent::input_error("DHT not enabled.");
++
++ int port, ret;
++ char dummy;
++ char host[1024];
++
++ ret = std::sscanf(arg.c_str(), "%1023[^:]:%i%c", host, &port, &dummy);
++
++ if (ret == 1)
++ port = 6881;
++ else if (ret != 2)
++ throw torrent::input_error("Could not parse host.");
++
++ if (port < 1 || port > 65535)
++ throw torrent::input_error("Invalid port number.");
++
++ torrent::connection_manager()->resolver()(host,
(int)rak::socket_address::pf_inet, SOCK_DGRAM, call_add_node_t(port));
++}
++
++void
+ apply_enable_trackers(int64_t arg) {
+ for (core::Manager::DListItr itr =
control->core()->download_list()->begin(), last =
control->core()->download_list()->end(); itr != last; ++itr) {
+ torrent::TrackerList tl = (*itr)->download()->tracker_list();
+@@ -324,6 +361,11 @@
+ ADD_COMMAND_VALUE_UN("enable_trackers",
std::ptr_fun(&apply_enable_trackers));
+ ADD_COMMAND_STRING_UN("encoding_list",
std::ptr_fun(&apply_encoding_list));
+
++ ADD_VARIABLE_VALUE("dht_port", 6881);
++ ADD_COMMAND_STRING_UN("dht",
rak::make_mem_fun(control->dht_manager(), &core::DhtManager::set_start));
++ ADD_COMMAND_STRING_UN("dht_add_node",
std::ptr_fun(&apply_dht_add_node));
++ ADD_COMMAND_VOID("dht_statistics",
rak::make_mem_fun(control->dht_manager(), &core::DhtManager::dht_statistics));
++
+ ADD_VARIABLE_BOOL("peer_exchange", false);
+




Archive powered by MHonArc 2.6.24.

Top of Page