From 5ef57265bf37d7d01fbe458c3426f213514bca69 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Tue, 1 Aug 2006 15:27:08 +0000 Subject: [PATCH] first version with DHT support. Limited pipelining used by url-seeds. Fixed one configuration problem on FreeBSD. --- ChangeLog | 19 +- Jamfile | 12 + Makefile.am | 2 + docs/index.html | 8 +- docs/index.rst | 7 +- docs/manual.html | 318 +++++--- docs/manual.rst | 236 ++++-- examples/client_test.cpp | 24 + include/Makefile.am | 23 +- include/libtorrent/kademlia/closest_nodes.hpp | 86 ++ include/libtorrent/kademlia/dht_tracker.hpp | 129 +++ include/libtorrent/kademlia/find_data.hpp | 95 +++ include/libtorrent/kademlia/logging.hpp | 146 ++++ include/libtorrent/kademlia/node.hpp | 185 +++++ include/libtorrent/kademlia/node_entry.hpp | 63 ++ include/libtorrent/kademlia/node_id.hpp | 60 ++ .../libtorrent/kademlia/packet_iterator.hpp | 95 +++ include/libtorrent/kademlia/refresh.hpp | 158 ++++ include/libtorrent/kademlia/routing_table.hpp | 224 +++++ include/libtorrent/kademlia/rpc_manager.hpp | 196 +++++ .../kademlia/traversal_algorithm.hpp | 149 ++++ include/libtorrent/peer_connection.hpp | 1 + include/libtorrent/random_sample.hpp | 72 ++ include/libtorrent/session.hpp | 13 + include/libtorrent/session_settings.hpp | 32 + include/libtorrent/socket.hpp | 20 + include/libtorrent/torrent.hpp | 8 +- include/libtorrent/torrent_info.hpp | 17 + include/libtorrent/tracker_manager.hpp | 3 +- m4/ax_boost_date-time.m4 | 3 +- m4/ax_boost_filesystem.m4 | 3 +- m4/ax_boost_program_options.m4 | 3 +- m4/ax_boost_regex.m4 | 3 +- m4/ax_boost_thread.m4 | 3 +- src/Makefile.am | 12 +- src/bt_peer_connection.cpp | 8 +- src/entry.cpp | 4 +- src/kademlia/closest_nodes.cpp | 146 ++++ src/kademlia/dht_tracker.cpp | 772 ++++++++++++++++++ src/kademlia/find_data.cpp | 156 ++++ src/kademlia/node.cpp | 501 ++++++++++++ src/kademlia/node_id.cpp | 97 +++ src/kademlia/refresh.cpp | 188 +++++ src/kademlia/routing_table.cpp | 417 ++++++++++ src/kademlia/rpc_manager.cpp | 343 ++++++++ src/kademlia/traversal_algorithm.cpp | 162 ++++ src/peer_connection.cpp | 4 + src/session.cpp | 74 +- src/torrent.cpp | 54 +- src/torrent_info.cpp | 60 +- src/tracker_manager.cpp | 7 + src/web_peer_connection.cpp | 2 + 52 files changed, 5211 insertions(+), 212 deletions(-) create mode 100644 include/libtorrent/kademlia/closest_nodes.hpp create mode 100644 include/libtorrent/kademlia/dht_tracker.hpp create mode 100644 include/libtorrent/kademlia/find_data.hpp create mode 100644 include/libtorrent/kademlia/logging.hpp create mode 100644 include/libtorrent/kademlia/node.hpp create mode 100644 include/libtorrent/kademlia/node_entry.hpp create mode 100644 include/libtorrent/kademlia/node_id.hpp create mode 100644 include/libtorrent/kademlia/packet_iterator.hpp create mode 100644 include/libtorrent/kademlia/refresh.hpp create mode 100644 include/libtorrent/kademlia/routing_table.hpp create mode 100644 include/libtorrent/kademlia/rpc_manager.hpp create mode 100644 include/libtorrent/kademlia/traversal_algorithm.hpp create mode 100644 include/libtorrent/random_sample.hpp create mode 100644 src/kademlia/closest_nodes.cpp create mode 100644 src/kademlia/dht_tracker.cpp create mode 100644 src/kademlia/find_data.cpp create mode 100644 src/kademlia/node.cpp create mode 100644 src/kademlia/node_id.cpp create mode 100644 src/kademlia/refresh.cpp create mode 100644 src/kademlia/routing_table.cpp create mode 100644 src/kademlia/rpc_manager.cpp create mode 100644 src/kademlia/traversal_algorithm.cpp diff --git a/ChangeLog b/ChangeLog index a2a636fb1..7aa57253c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,18 @@ - + * added support for trackerless torrents (with kademlia DHT). + * support for torrents with the private flag set. + * support for torrents containing bootstrap nodes for the + DHT network. + * fixed problem with the configure script on FreeBSD. + * limits the pipelining used on url-seeds. + * fixed problem where the shutdown always would delay for + session_settings::stop_tracker_timeout seconds. * session::listen_on() won't reopen the socket in case the port and - interface is the same as is already open. - * Added http proxy support for web seeds. - * Fixed problem where upload and download stats could become incorrect + interface is the same as the one currently in use. + * added http proxy support for web seeds. + * fixed problem where upload and download stats could become incorrect in case of high cpu load. - * Added more clients to the identifiable list. - * Fixed fingerprint parser to cope with latest Mainline versions. + * added more clients to the identifiable list. + * fixed fingerprint parser to cope with latest Mainline versions. release 0.10 diff --git a/Jamfile b/Jamfile index d8e7b8558..700daf443 100755 --- a/Jamfile +++ b/Jamfile @@ -27,6 +27,7 @@ project torrent ./zlib $(BOOST_ROOT) release:NDEBUG + debug:TORRENT_DHT_VERBOSE_LOGGING BOOST_ALL_NO_LIB _FILE_OFFSET_BITS=64 BOOST_THREAD_USE_LIB @@ -54,6 +55,7 @@ project torrent ./include $(BOOST_ROOT) release:NDEBUG + debug:TORRENT_DHT_VERBOSE_LOGGING BOOST_ALL_NO_LIB shared:TORRENT_LINKING_SHARED @@ -82,6 +84,16 @@ SOURCES = http_tracker_connection.cpp udp_tracker_connection.cpp sha1.cpp + + kademlia/closest_nodes.cpp + kademlia/dht_tracker.cpp + kademlia/node.cpp + kademlia/refresh.cpp + kademlia/rpc_manager.cpp + kademlia/find_data.cpp + kademlia/node_id.cpp + kademlia/routing_table.cpp + kademlia/traversal_algorithm.cpp ; ZLIB_SOURCES = diff --git a/Makefile.am b/Makefile.am index 6a5699b74..d5d13d85b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,8 +4,10 @@ docs/extension_protocol.html docs/udp_tracker_protocol.rst \ docs/projects.rst docs/projects.html \ docs/arctic_thumb.png \ docs/bitbuddy_thumb.jpg \ +docs/bitscast_thumb.png \ docs/bitslug_thumb.png \ docs/btg_thumb.jpg \ +docs/electric_sheep_thumb.jpg \ docs/moopolice_thumb.gif \ docs/qbittorrent_thumb.jpg \ docs/ziptorrent_thumb.gif \ diff --git a/docs/index.html b/docs/index.html index 57a51973e..45206e020 100755 --- a/docs/index.html +++ b/docs/index.html @@ -12,11 +12,12 @@

libtorrent

--+++-+ @@ -24,6 +25,7 @@ + diff --git a/docs/index.rst b/docs/index.rst index 80d4fa452..3978ff28e 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,15 +5,16 @@ libtorrent .. class:: menu -=================== ============== ============== =========== =============== -`sourceforge page`_ documentation_ `report bugs`_ screenshot_ `mailing list`_ -=================== ============== ============== =========== =============== +=================== ============== ============== =========== =============== ========================== +`sourceforge page`_ documentation_ `report bugs`_ screenshot_ `mailing list`_ `who's using libtorrent?`_ +=================== ============== ============== =========== =============== ========================== .. _sourceforge page: http://www.sourceforge.net/projects/libtorrent .. _documentation: manual.html .. _`report bugs`: http://sourceforge.net/tracker/?group_id=79942&atid=558250 .. _screenshot: client_test.png .. _mailing list: http://lists.sourceforge.net/lists/listinfo/libtorrent-discuss +.. _`who's using libtorrent?`: projects.html libtorrent is a C++ library that aims to be a good alternative to all the other bittorrent implementations around. It is a diff --git a/docs/manual.html b/docs/manual.html index 219b51a0b..446e6f051 100755 --- a/docs/manual.html +++ b/docs/manual.html @@ -46,127 +46,131 @@
  • status()
  • is_listening() listen_port() listen_on()
  • pop_alert() set_severity_level()
  • +
  • start_dht() stop_dht() set_dht_settings() dht_state()
  • -
  • entry
      -
    • integer() string() list() dict() type()
    • -
    • operator[]
    • -
    • find_key()
    • +
    • entry
    • -
    • torrent_info
        -
      • torrent_info()
      • -
      • set_comment() set_piece_size() set_creator() set_hash() add_tracker() add_file()
      • -
      • create_torrent()
      • -
      • begin_files() end_files() rbegin_files() rend_files()
      • -
      • num_files() file_at()
      • -
      • map_block()
      • -
      • map_file()
      • -
      • url_seeds()
      • -
      • print()
      • -
      • trackers()
      • -
      • total_size() piece_length() piece_size() num_pieces()
      • -
      • hash_for_piece() info_hash()
      • -
      • name() comment() creation_date() creator()
      • +
      • torrent_info
      • -
      • torrent_handle
          -
        • file_progress()
        • -
        • save_path()
        • -
        • move_storage()
        • -
        • force_reannounce()
        • -
        • connect_peer()
        • -
        • set_ratio()
        • -
        • set_upload_limit() set_download_limit()
        • -
        • set_peer_upload_limit() set_peer_download_limit()
        • -
        • pause() resume() is_paused()
        • -
        • is_seed()
        • -
        • has_metadata()
        • -
        • set_tracker_login()
        • -
        • trackers() replace_trackers()
        • -
        • add_url_seed()
        • -
        • use_interface()
        • -
        • info_hash()
        • -
        • set_max_uploads() set_max_connections()
        • -
        • write_resume_data()
        • -
        • metadata()
        • -
        • status()
        • -
        • get_download_queue()
        • -
        • get_peer_info()
        • -
        • get_torrent_info()
        • -
        • is_valid()
        • +
        • torrent_handle
        • -
        • torrent_status
        • -
        • peer_info
        • -
        • session_settings
        • -
        • ip_filter
            -
          • ip_filter()
          • -
          • add_rule()
          • -
          • access()
          • -
          • export_filter()
          • +
          • torrent_status
          • +
          • peer_info
          • +
          • session_settings
          • +
          • ip_filter
          • -
          • big_number
          • -
          • hasher
          • -
          • fingerprint
          • -
          • free functions
              -
            • identify_client()
            • -
            • client_fingerprint()
            • -
            • bdecode() bencode()
            • +
            • big_number
            • +
            • hasher
            • +
            • fingerprint
            • +
            • free functions
            • -
            • alerts
                -
              • listen_failed_alert
              • -
              • file_error_alert
              • -
              • tracker_announce_alert
              • -
              • tracker_alert
              • -
              • tracker_reply_alert
              • -
              • tracker_warning_alert
              • -
              • url_seed_alert
              • -
              • hash_failed_alert
              • -
              • peer_ban_alert
              • -
              • peer_error_alert
              • -
              • invalid_request_alert
              • -
              • torrent_finished_alert
              • -
              • metadata_failed_alert
              • -
              • metadata_received_alert
              • -
              • fastresume_rejected_alert
              • -
              • dispatcher
              • +
              • alerts
              • -
              • exceptions
                  -
                • invalid_handle
                • -
                • duplicate_torrent
                • -
                • invalid_encoding
                • -
                • type_error
                • -
                • invalid_torrent_file
                • +
                • exceptions
                • -
                • examples
                    -
                  • dump_torrent
                  • -
                  • simple client
                  • -
                  • make_torrent
                  • +
                  • examples
                  • -
                  • fast resume
                      -
                    • file format
                    • +
                    • fast resume
                    • -
                    • threads
                    • -
                    • storage allocation
                        -
                      • full allocation
                      • -
                      • compact allocation
                      • +
                      • threads
                      • +
                      • storage allocation
                      • -
                      • extensions
                        @@ -185,6 +189,7 @@ example client.

                        project (including this documentation). The current state includes the following features:

                          +
                        • Trackerless torrents (using a kademlia DHT)
                        • multitracker extension support (as specified by John Hoffman)
                        • serves multiple torrents on a single port and in a single thread
                        • gzipped tracker-responses
                        • @@ -580,6 +585,10 @@ should be defined when building libtorrent as a shared library. (This is set by the Jamfile when link=shared is set). +TORRENT_DISABLE_DHT +If this is defined, the support for trackerless +torrents will be disabled. +

                          If you experience that libtorrent uses unreasonable amounts of cpu, it will @@ -667,9 +676,15 @@ class session: public boost::noncopyable std::pair<int, int> const& port_range , char const* interface = 0); - std::auto_ptr<alert> pop_alert(); void set_severity_level(alert::severity_t s); + + void start_dht(); + void stop_dht(); + void set_dht_settings(dht_settings const& settings); + entry dht_state() const; + void add_dht_node(std::pair<std::string, int> const& node); + };

                          Once it's created, the session object will spawn the main thread that will do all the work. @@ -921,6 +936,60 @@ void set_severity_level(alert::severity_t s); set_severity_level() you can filter how serious the event has to be for you to receive it through pop_alert(). For information, see alerts.

                        +
                        +

                        start_dht() stop_dht() set_dht_settings() dht_state()

                        +
                        +
                        +void start_dht(entry const& startup_state);
                        +void stop_dht();
                        +void set_dht_settings(dht_settings const& settings);
                        +entry dht_state() const;
                        +void add_dht_node(std::pair<std::string, int> const& node);
                        +
                        +
                        +

                        These functions are not available in case TORRENT_DISABLE_DHT is +defined. start_dht starts the dht node and makes the trackerless service +available to torrents. The startup state is optional and can contain nodes +and the node id from the previous session. The dht node state is a bencoded +dictionary with the following entries:

                        +
                        +
                        nodes
                        +
                        is a string with the nodes written as 6 bytes each. 4 bytes ip +address and 2 bytes port number. Both are written in big endian byte order.
                        +
                        node-id
                        +
                        The node id written as a readable string as a hexadecimal number.
                        +
                        +

                        dht_state will return the current state of the dht node, this can be used +to start up the node again, passing this entry to start_dht. It is a good +idea to save this to disk when the session is closed, and read it up again +when starting.

                        +

                        stop_dht stops the dht node.

                        +

                        add_dht_node adds a node to the routing table. This can be used if your +client has its own source of bootstrapping nodes.

                        +

                        set_dht_settings sets some parameters availavle to the dht node. The +struct has the following members:

                        +
                        +struct dht_settings
                        +{
                        +        int max_peers_reply;
                        +        int search_branching;
                        +        int service_port;
                        +        int max_fail_count;
                        +};
                        +
                        +

                        max_peers_reply is the maximum number of peers the node will send in +response to a get_peers message from another node.

                        +

                        search_branching is the number of concurrent search request the node will +send when announcing and refreshing the routing table. This parameter is +called alpha in the kademlia paper.

                        +

                        service_port is the udp port the node will listen to. (currently this +cannot be changed while the node is running).

                        +

                        max_fail_count is the maximum number of failed tries to contact a node +before it is removed from the routing table. If there are known working nodes +that are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node that can +replace them.

                        +

                        entry

                        @@ -1113,6 +1182,9 @@ public: std::vector<announce_entry> const& trackers() const; + bool priv() const; + void set_priv(bool v); + std::vector<std::string> const& url_seeds() const; size_type total_size() const; @@ -1123,6 +1195,9 @@ public: std::string const& comment() const; std::string const& creator() const; + std::vector<std::pair<std::string, int> > const& nodes() const; + void add_node(std::pair<std::string, int> const& node); + boost::optional<boost::posix_time::ptime> creation_date() const; @@ -1201,6 +1276,8 @@ You can save this data as a torrent file with bencode() (see make_torrent.

                        This function is not const because it will also set the info-hash of the torrent_info object.

                        +

                        Note that a torrent file must include at least one file, and it must have at +least one tracker url or at least one DHT node.

                        begin_files() end_files() rbegin_files() rend_files()

                        @@ -1390,6 +1467,38 @@ in the torrent file, this will return a date of January 1:st 1970.

                        creator() returns the creator string in the torrent. If there is no creator string it will return an empty string.

                        +
                        +

                        priv() set_priv()

                        +
                        +
                        +bool priv() const;
                        +void set_priv(bool v);
                        +
                        +
                        +

                        priv() returns true if this torrent is private. i.e., it should not be +distributed on the trackerless network (the kademlia DHT).

                        +

                        set_priv() sets or clears the private flag on this torrent.

                        +
                        +
                        +

                        nodes()

                        +
                        +
                        +std::vector<std::pair<std::string, int> > const& nodes() const;
                        +
                        +
                        +

                        If this torrent contains any DHT nodes, they are put in this vector in their original +form (host name and port number).

                        +
                        +
                        +

                        add_node()

                        +
                        +
                        +void add_node(std::pair<std::string, int> const& node);
                        +
                        +
                        +

                        This is used when creating torrent. Use this to add a known DHT node. It may +be used, by the client, to bootstrap into the DHT network.

                        +

                        torrent_handle

                        @@ -2141,6 +2250,7 @@ struct session_settings int whole_pieces_threshold; int peer_timeout; int urlseed_timeout; + int urlseed_pipeline_size; };

                        proxy_ip may be a hostname or ip to a http proxy to use. If this is @@ -2204,6 +2314,10 @@ in the protocol specification. After half the time out, a keep alive message is sent.

                        urlseed_timeout is the same as peer_timeout but applies only to url seeds. This value defaults to 20 seconds.

                        +

                        urlseed_pipeline_size controls the pipelining with the web server. When +using persistent connections to HTTP 1.1 servers, the client is allowed to +send more requests before the first response is received. This number controls +the number of outstanding requests to use with url-seeds. Default is 5.

                        ip_filter

                        @@ -3360,7 +3474,6 @@ bittorrent client.

                        Extension name: "chat"

                        The payload in the packet is a bencoded dictionary with any combination of the following entries:

                        -
                        @@ -3381,7 +3494,6 @@ Any unrecognized strings should be ignored.
                        -

                        metadata from peers

                        @@ -3402,7 +3514,6 @@ are put as payload to the extension message. The three packets are:

                      request metadata:

                      -
                      @@ -3441,9 +3552,7 @@ metadata.
                      -

                      metadata:

                      -
                      @@ -3481,9 +3590,7 @@ protocol packet.
                      -

                      Don't have metadata:

                      -
                      @@ -3506,7 +3613,6 @@ doesn't have any metadata.
                      -

                      HTTP seeding

                      diff --git a/docs/manual.rst b/docs/manual.rst index fd303715d..4887f01f1 100755 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -27,6 +27,7 @@ libtorrent is still being developed, however it is stable. It is an ongoing project (including this documentation). The current state includes the following features: +* Trackerless torrents (using a kademlia DHT) * multitracker extension support (as `specified by John Hoffman`__) * serves multiple torrents on a single port and in a single thread * gzipped tracker-responses @@ -474,6 +475,9 @@ defines you can use to control the build. | | a shared library. (This is set by the Jamfile | | | when ``link=shared`` is set). | +--------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_DHT`` | If this is defined, the support for trackerless | +|Ê | torrents will be disabled. | ++--------------------------------+-------------------------------------------------+ If you experience that libtorrent uses unreasonable amounts of cpu, it will @@ -558,9 +562,15 @@ The ``session`` class has the following synopsis:: std::pair const& port_range , char const* interface = 0); - std::auto_ptr pop_alert(); void set_severity_level(alert::severity_t s); + + void start_dht(); + void stop_dht(); + void set_dht_settings(dht_settings const& settings); + entry dht_state() const; + void add_dht_node(std::pair const& node); + }; Once it's created, the session object will spawn the main thread that will do all the work. @@ -836,6 +846,68 @@ pop_alert() set_severity_level() receive it through ``pop_alert()``. For information, see alerts_. +start_dht() stop_dht() set_dht_settings() dht_state() +----------------------------------------------------- + + :: + + void start_dht(entry const& startup_state); + void stop_dht(); + void set_dht_settings(dht_settings const& settings); + entry dht_state() const; + void add_dht_node(std::pair const& node); + +These functions are not available in case ``TORRENT_DISABLE_DHT`` is +defined. ``start_dht`` starts the dht node and makes the trackerless service +available to torrents. The startup state is optional and can contain nodes +and the node id from the previous session. The dht node state is a bencoded +dictionary with the following entries: + +``nodes`` + is a string with the nodes written as 6 bytes each. 4 bytes ip + address and 2 bytes port number. Both are written in big endian byte order. + +``node-id`` + The node id written as a readable string as a hexadecimal number. + +``dht_state`` will return the current state of the dht node, this can be used +to start up the node again, passing this entry to ``start_dht``. It is a good +idea to save this to disk when the session is closed, and read it up again +when starting. + +``stop_dht`` stops the dht node. + +``add_dht_node`` adds a node to the routing table. This can be used if your +client has its own source of bootstrapping nodes. + +``set_dht_settings`` sets some parameters availavle to the dht node. The +struct has the following members:: + + struct dht_settings + { + int max_peers_reply; + int search_branching; + int service_port; + int max_fail_count; + }; + +``max_peers_reply`` is the maximum number of peers the node will send in +response to a ``get_peers`` message from another node. + +``search_branching`` is the number of concurrent search request the node will +send when announcing and refreshing the routing table. This parameter is +called alpha in the kademlia paper. + +``service_port`` is the udp port the node will listen to. (currently this +cannot be changed while the node is running). + +``max_fail_count`` is the maximum number of failed tries to contact a node +before it is removed from the routing table. If there are known working nodes +that are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node that can +replace them. + + entry ===== @@ -1041,6 +1113,9 @@ The ``torrent_info`` has the following synopsis:: std::vector const& trackers() const; + bool priv() const; + void set_priv(bool v); + std::vector const& url_seeds() const; size_type total_size() const; @@ -1051,6 +1126,9 @@ The ``torrent_info`` has the following synopsis:: std::string const& comment() const; std::string const& creator() const; + std::vector > const& nodes() const; + void add_node(std::pair const& node); + boost::optional creation_date() const; @@ -1141,6 +1219,9 @@ complete example, see make_torrent_. This function is not const because it will also set the info-hash of the ``torrent_info`` object. +Note that a torrent file must include at least one file, and it must have at +least one tracker url or at least one DHT node. + begin_files() end_files() rbegin_files() rend_files() ----------------------------------------------------- @@ -1354,6 +1435,42 @@ it will return an empty string. __ http://www.boost.org/libs/date_time/doc/class_ptime.html +priv() set_priv() +----------------- + + :: + + bool priv() const; + void set_priv(bool v); + +``priv()`` returns true if this torrent is private. i.e., it should not be +distributed on the trackerless network (the kademlia DHT). + +``set_priv()`` sets or clears the private flag on this torrent. + + +nodes() +------- + + :: + + std::vector > const& nodes() const; + +If this torrent contains any DHT nodes, they are put in this vector in their original +form (host name and port number). + + +add_node() +---------- + + :: + + void add_node(std::pair const& node); + +This is used when creating torrent. Use this to add a known DHT node. It may +be used, by the client, to bootstrap into the DHT network. + + torrent_handle ============== @@ -2146,6 +2263,7 @@ that will be sent to the tracker. The user-agent is a good way to identify your int whole_pieces_threshold; int peer_timeout; int urlseed_timeout; + int urlseed_pipeline_size; }; ``proxy_ip`` may be a hostname or ip to a http proxy to use. If this is @@ -2225,6 +2343,10 @@ is sent. ``urlseed_timeout`` is the same as ``peer_timeout`` but applies only to url seeds. This value defaults to 20 seconds. +``urlseed_pipeline_size`` controls the pipelining with the web server. When +using persistent connections to HTTP 1.1 servers, the client is allowed to +send more requests before the first response is received. This number controls +the number of outstanding requests to use with url-seeds. Default is 5. ip_filter ========= @@ -3476,17 +3598,17 @@ Extension name: "chat" The payload in the packet is a bencoded dictionary with any combination of the following entries: - +----------+--------------------------------------------------------+ - | "msg" | This is a string that contains a message that | - | | should be displayed to the user. | - +----------+--------------------------------------------------------+ - | "ctrl" | This is a control string that can tell a client that | - | | it is ignored (to make the user aware of that) and | - | | it can also tell a client that it is no longer ignored.| - | | These notifications are encoded as the strings: | - | | "ignored" and "not ignored". | - | | Any unrecognized strings should be ignored. | - +----------+--------------------------------------------------------+ ++----------+--------------------------------------------------------+ +| "msg" | This is a string that contains a message that | +| | should be displayed to the user. | ++----------+--------------------------------------------------------+ +| "ctrl" | This is a control string that can tell a client that | +| | it is ignored (to make the user aware of that) and | +| | it can also tell a client that it is no longer ignored.| +| | These notifications are encoded as the strings: | +| | "ignored" and "not ignored". | +| | Any unrecognized strings should be ignored. | ++----------+--------------------------------------------------------+ metadata from peers ------------------- @@ -3510,57 +3632,57 @@ are put as payload to the extension message. The three packets are: request metadata: - +-----------+---------------+----------------------------------------+ - | size | name | description | - +===========+===============+========================================+ - | uint8_t | msg_type | Determines the kind of message this is | - | | | 0 means 'request metadata' | - +-----------+---------------+----------------------------------------+ - | uint8_t | start | The start of the metadata block that | - | | | is requested. It is given in 256:ths | - | | | of the total size of the metadata, | - | | | since the requesting client don't know | - | | | the size of the metadata. | - +-----------+---------------+----------------------------------------+ - | uint8_t | size | The size of the metadata block that is | - | | | requested. This is also given in | - | | | 256:ths of the total size of the | - | | | metadata. The size is given as size-1. | - | | | That means that if this field is set | - | | | 0, the request wants one 256:th of the | - | | | metadata. | - +-----------+---------------+----------------------------------------+ ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | Determines the kind of message this is | +| | | 0 means 'request metadata' | ++-----------+---------------+----------------------------------------+ +| uint8_t | start | The start of the metadata block that | +| | | is requested. It is given in 256:ths | +| | | of the total size of the metadata, | +| | | since the requesting client don't know | +| | | the size of the metadata. | ++-----------+---------------+----------------------------------------+ +| uint8_t | size | The size of the metadata block that is | +| | | requested. This is also given in | +| | | 256:ths of the total size of the | +| | | metadata. The size is given as size-1. | +| | | That means that if this field is set | +| | | 0, the request wants one 256:th of the | +| | | metadata. | ++-----------+---------------+----------------------------------------+ metadata: - +-----------+---------------+----------------------------------------+ - | size | name | description | - +===========+===============+========================================+ - | uint8_t | msg_type | 1 means 'metadata' | - +-----------+---------------+----------------------------------------+ - | int32_t | total_size | The total size of the metadata, given | - | | | in number of bytes. | - +-----------+---------------+----------------------------------------+ - | int32_t | offset | The offset of where the metadata block | - | | | in this message belongs in the final | - | | | metadata. This is given in bytes. | - +-----------+---------------+----------------------------------------+ - | uint8_t[] | metadata | The actual metadata block. The size of | - | | | this part is given implicit by the | - | | | length prefix in the bittorrent | - | | | protocol packet. | - +-----------+---------------+----------------------------------------+ ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | 1 means 'metadata' | ++-----------+---------------+----------------------------------------+ +| int32_t | total_size | The total size of the metadata, given | +| | | in number of bytes. | ++-----------+---------------+----------------------------------------+ +| int32_t | offset | The offset of where the metadata block | +| | | in this message belongs in the final | +| | | metadata. This is given in bytes. | ++-----------+---------------+----------------------------------------+ +| uint8_t[] | metadata | The actual metadata block. The size of | +| | | this part is given implicit by the | +| | | length prefix in the bittorrent | +| | | protocol packet. | ++-----------+---------------+----------------------------------------+ Don't have metadata: - +-----------+---------------+----------------------------------------+ - | size | name | description | - +===========+===============+========================================+ - | uint8_t | msg_type | 2 means 'I don't have metadata'. | - | | | This message is sent as a reply to a | - | | | metadata request if the the client | - | | | doesn't have any metadata. | - +-----------+---------------+----------------------------------------+ ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | 2 means 'I don't have metadata'. | +| | | This message is sent as a reply to a | +| | | metadata request if the the client | +| | | doesn't have any metadata. | ++-----------+---------------+----------------------------------------+ HTTP seeding ------------ diff --git a/examples/client_test.cpp b/examples/client_test.cpp index abce44b10..be98ae6ee 100755 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -566,6 +566,23 @@ int main(int ac, char* av[]) handles_t handles; session ses; + boost::filesystem::ifstream dht_state_file(".dht_state" + , std::ios_base::binary); + dht_state_file.unsetf(std::ios_base::skipws); + try + { + entry dht_state = bdecode( + std::istream_iterator(dht_state_file) + , std::istream_iterator()); + ses.start_dht(dht_state); + } + catch (std::exception&) + { + ses.start_dht(); + ses.add_dht_node(std::make_pair(std::string("router.bittorrent.com") + , 6881)); + } + ses.set_max_half_open_connections(half_open_limit); ses.set_download_rate_limit(download_limit); ses.set_upload_rate_limit(upload_limit); @@ -914,11 +931,18 @@ int main(int ac, char* av[]) next_dir_scan = second_clock::universal_time() + seconds(poll_interval); } } + + entry dht_state = ses.dht_state(); + boost::filesystem::ofstream out(".dht_state" + , std::ios_base::binary); + out.unsetf(std::ios_base::skipws); + bencode(std::ostream_iterator(out), dht_state); } catch (std::exception& e) { std::cout << e.what() << "\n"; } + #ifdef TORRENT_PROFILE print_checkpoints(); #endif diff --git a/include/Makefile.am b/include/Makefile.am index 2f3ef1902..0a85c3154 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -25,6 +25,7 @@ libtorrent/peer_request.hpp \ libtorrent/piece_block_progress.hpp \ libtorrent/piece_picker.hpp \ libtorrent/policy.hpp \ +libtorrent/random_sample.hpp \ libtorrent/resource_request.hpp \ libtorrent/session.hpp \ libtorrent/session_settings.hpp \ @@ -40,6 +41,19 @@ libtorrent/udp_tracker_connection.hpp \ libtorrent/utf8.hpp \ libtorrent/version.hpp \ \ +libtorrent/kademlia/closest_nodes.hpp \ +libtorrent/kademlia/dht_tracker.hpp \ +libtorrent/kademlia/find_data.hpp \ +libtorrent/kademlia/logging.hpp \ +libtorrent/kademlia/node.hpp \ +libtorrent/kademlia/node_entry.hpp \ +libtorrent/kademlia/node_id.hpp \ +libtorrent/kademlia/packet_iterator.hpp \ +libtorrent/kademlia/refresh.hpp \ +libtorrent/kademlia/routing_table.hpp \ +libtorrent/kademlia/rpc_manager.hpp \ +libtorrent/kademlia/traversal_algorithm.hpp \ +\ libtorrent/asio/basic_datagram_socket.hpp \ libtorrent/asio/basic_deadline_timer.hpp \ libtorrent/asio/basic_io_object.hpp \ @@ -68,11 +82,13 @@ libtorrent/asio/detail/buffered_stream_storage.hpp \ libtorrent/asio/detail/call_stack.hpp \ libtorrent/asio/detail/const_buffers_iterator.hpp \ libtorrent/asio/detail/consuming_buffers.hpp \ +libtorrent/asio/detail/deadline_timer_service.hpp \ libtorrent/asio/detail/epoll_reactor.hpp \ libtorrent/asio/detail/epoll_reactor_fwd.hpp \ libtorrent/asio/detail/event.hpp \ libtorrent/asio/detail/fd_set_adapter.hpp \ libtorrent/asio/detail/handler_alloc_helpers.hpp \ +libtorrent/asio/detail/handler_dispatch_helpers.hpp \ libtorrent/asio/detail/hash_map.hpp \ libtorrent/asio/detail/io_control.hpp \ libtorrent/asio/detail/kqueue_reactor.hpp \ @@ -112,6 +128,8 @@ libtorrent/asio/detail/strand_service.hpp \ libtorrent/asio/detail/task_io_service.hpp \ libtorrent/asio/detail/task_io_service_fwd.hpp \ libtorrent/asio/detail/thread.hpp \ +libtorrent/asio/detail/timer_queue.hpp \ +libtorrent/asio/detail/timer_queue_base.hpp \ libtorrent/asio/detail/tss_ptr.hpp \ libtorrent/asio/detail/win_event.hpp \ libtorrent/asio/detail/win_iocp_io_service.hpp \ @@ -128,6 +146,7 @@ libtorrent/asio/detail/wrapped_handler.hpp \ libtorrent/asio/error.hpp \ libtorrent/asio/error_handler.hpp \ libtorrent/asio/handler_alloc_hook.hpp \ +libtorrent/asio/handler_dispatch_hook.hpp \ libtorrent/asio/impl/io_service.ipp \ libtorrent/asio/impl/read.ipp \ libtorrent/asio/impl/read_until.ipp \ @@ -174,6 +193,6 @@ libtorrent/asio/streambuf.hpp \ libtorrent/asio/system_exception.hpp \ libtorrent/asio/thread.hpp \ libtorrent/asio/time_traits.hpp \ -libtorrent/asio/write.hpp \ -libtorrent/asio.hpp +libtorrent/asio/write.hpp + diff --git a/include/libtorrent/kademlia/closest_nodes.hpp b/include/libtorrent/kademlia/closest_nodes.hpp new file mode 100644 index 000000000..d5580b9c9 --- /dev/null +++ b/include/libtorrent/kademlia/closest_nodes.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef CLOSEST_NODES_050323_HPP +#define CLOSEST_NODES_050323_HPP + +#include + +#include +#include +#include + +#include + +namespace libtorrent { namespace dht +{ + +class rpc_manager; + +// -------- closest nodes ----------- + +class closest_nodes : public traversal_algorithm +{ +public: + typedef boost::function< + void(std::vector const&) + > done_callback; + + static void initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + +private: + void done(); + void invoke(node_id const& id, asio::ip::udp::endpoint addr); + + closest_nodes( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + + done_callback m_done_callback; +}; + +} } // namespace libtorrent::dht + +#endif // CLOSEST_NODES_050323_HPP + diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp new file mode 100644 index 000000000..0b7e243d9 --- /dev/null +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -0,0 +1,129 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_DHT + +#ifndef TORRENT_DHT_TRACKER +#define TORRENT_DHT_TRACKER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/packet_iterator.hpp" +#include "libtorrent/session_settings.hpp" + +namespace libtorrent { namespace dht +{ + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_DECLARE_LOG(dht_tracker); +#endif + + struct dht_tracker + { + dht_tracker(asio::io_service& d, dht_settings const& settings + , entry const& bootstrap); + + void add_node(udp::endpoint node); + void add_node(std::pair const& node); + + entry state() const; + + void announce(sha1_hash const& ih, int listen_port + , boost::function const& + , sha1_hash const&)> f); + + private: + + void on_name_lookup(asio::error const& e + , udp::resolver::iterator host); + void second_tick(asio::error const& e); + void tick(asio::error const& e); + + // translate bittorrent kademlia message into the generice kademlia message + // used by the library + void on_receive(asio::error const& error, size_t bytes_transferred); + void on_bootstrap(); + void send_packet(msg const& m); + + asio::io_service& m_demuxer; + asio::ip::udp::socket m_socket; + + node_impl m_dht; + + // this is the index of the receive buffer we are currently receiving to + // the other buffer is the one containing the last message + int m_buffer; + std::vector m_in_buf[2]; + udp::endpoint m_remote_endpoint[2]; + std::vector m_send_buf; + + boost::posix_time::ptime m_last_refresh; + deadline_timer m_timer; + deadline_timer m_second_timer; + dht_settings const& m_settings; + int m_refresh_bucket; + + // used to resolve hostnames for nodes + udp::resolver m_host_resolver; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + int m_replies_sent[5]; + int m_queries_received[5]; + int m_replies_bytes_sent[5]; + int m_queries_bytes_received[5]; + int m_counter; + int m_announces; + int m_failed_announces; + + int m_total_message_input; + int m_ut_message_input; + int m_lt_message_input; + int m_mp_message_input; + int m_gr_message_input; +#endif + }; +}} + +#endif +#endif diff --git a/include/libtorrent/kademlia/find_data.hpp b/include/libtorrent/kademlia/find_data.hpp new file mode 100644 index 000000000..bbafcdd77 --- /dev/null +++ b/include/libtorrent/kademlia/find_data.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FIND_DATA_050323_HPP +#define FIND_DATA_050323_HPP + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +typedef std::vector packet_t; + +class rpc_manager; + +// -------- find data ----------- + +class find_data : public traversal_algorithm +{ +public: + typedef boost::function done_callback; + + static void initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + + void got_data(msg const* m); + +private: + void done(); + void invoke(node_id const& id, udp::endpoint addr); + + find_data( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback + ); + + done_callback m_done_callback; + boost::shared_ptr m_packet; + bool m_done; +}; + +} } // namespace libtorrent::dht + +#endif // FIND_DATA_050323_HPP + diff --git a/include/libtorrent/kademlia/logging.hpp b/include/libtorrent/kademlia/logging.hpp new file mode 100644 index 000000000..8bd488f1a --- /dev/null +++ b/include/libtorrent/kademlia/logging.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LOGGING_HPP +#define TORRENT_LOGGING_HPP + +#include +#include + +namespace libtorrent { namespace dht +{ + +class log +{ +public: + log(char const* id, std::ostream& stream) + : m_id(id) + , m_enabled(true) + , m_stream(stream) + { + } + + char const* id() const + { + return m_id; + } + + bool enabled() const + { + return m_enabled; + } + + void enable(bool e) + { + m_enabled = e; + } + + void flush() { m_stream.flush(); } + + template + log& operator<<(T const& x) + { + m_stream << x; + return *this; + } + +private: + char const* m_id; + bool m_enabled; + std::ostream& m_stream; +}; + +class log_event +{ +public: + log_event(log& log) + : log_(log) + { + if (log_.enabled()) + log_ << '[' << log.id() << "] "; + } + + ~log_event() + { + if (log_.enabled()) + { + log_ << "\n"; + log_.flush(); + } + } + + template + log_event& operator<<(T const& x) + { + log_ << x; + return *this; + } + + operator bool() const + { + return log_.enabled(); + } + +private: + log& log_; +}; + +class inverted_log_event : public log_event +{ +public: + inverted_log_event(log& log) : log_event(log) {} + + operator bool() const + { + return !log_event::operator bool(); + } +}; + +} } // namespace libtorrent::dht + +#define TORRENT_DECLARE_LOG(name) \ + libtorrent::dht::log& name ## _log() + +#define TORRENT_DEFINE_LOG(name) \ + libtorrent::dht::log& name ## _log() \ + { \ + static std::ofstream log_file("libtorrent_logs/dht.log", std::ios::app); \ + static libtorrent::dht::log instance(#name, log_file); \ + return instance; \ + } + +#define TORRENT_LOG(name) \ + if (libtorrent::dht::inverted_log_event event_object__ = name ## _log()); \ + else static_cast(event_object__) + +#endif + diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp new file mode 100644 index 000000000..0187c06ef --- /dev/null +++ b/include/libtorrent/kademlia/node.hpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NODE_HPP +#define NODE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(node); +#endif + +// this is the entry for every peer +// the timestamp is there to make it possible +// to remove stale peers +struct peer_entry +{ + tcp::endpoint addr; + boost::posix_time::ptime added; +}; + +// this is a group. It contains a set of group members +struct torrent_entry +{ + std::set peers; +}; + +inline bool operator<(peer_entry const& lhs, peer_entry const& rhs) +{ + return lhs.addr.address() == rhs.addr.address() + ? lhs.addr.port() < rhs.addr.port() + : lhs.addr.address() < rhs.addr.address(); +} + +struct null_type {}; + +class node_impl : boost::noncopyable +{ +typedef std::map table_t; +public: + node_impl(boost::function const& f + , dht_settings const& settings, boost::optional node_id); + + virtual ~node_impl() {} + + void refresh(node_id const& id, boost::function0 f); + void bootstrap(std::vector const& nodes + , boost::function0 f); + void find_node(node_id const& id, boost::function< + void(std::vector const&)> f); + + void incoming(msg const& m); + + void refresh(); + void refresh_bucket(int bucket); + int bucket_size(int bucket); + + typedef routing_table::iterator iterator; + + iterator begin() const { return m_table.begin(); } + iterator end() const { return m_table.end(); } + + typedef table_t::iterator data_iterator; + + node_id const& nid() const { return m_id; } + boost::tuple size() const{ return m_table.size(); } + + data_iterator begin_data() { return m_map.begin(); } + data_iterator end_data() { return m_map.end(); } + + void print_state(std::ostream& os) const + { m_table.print_state(os); } + + void announce(sha1_hash const& info_hash, int listen_port + , boost::function const& + , sha1_hash const&)> f); + + bool verify_token(msg const& m); + entry generate_token(msg const& m); + + // the returned time is the delay until tick should be called + // again the next time + boost::posix_time::time_duration tick(); + + // checks the buckets for any that needs refreshing + void check_refresh(); + + // generates a new secret number used to generate write tokens + void new_write_key(); + + // pings the given node, and adds it to + // the routing table if it respons and if the + // bucket is not full. + void add_node(udp::endpoint node); + + void replacement_cache(bucket_t& nodes) const + { m_table.replacement_cache(nodes); } + +protected: + // is called when a find data request is received. Should + // return false if the data is not stored on this node. If + // the data is stored, it should be serialized into 'data'. + bool on_find(msg const& m, std::vector& peers) const; + + // this is called when a store request is received. The data + // is store-parameters and the data to be stored. + void on_announce(msg const& m, msg& reply); + + dht_settings const& m_settings; + + // the maximum number of peers to send in a get_peers + // reply. Ordinary trackers usually limit this to 50. + // 50 => 6 * 50 = 250 bytes + packet overhead + int m_max_peers_reply; + +private: + void incoming_request(msg const& h); + + node_id m_id; + routing_table m_table; + rpc_manager m_rpc; + table_t m_map; + + boost::posix_time::ptime m_last_tracker_tick; + + // secret random numbers used to create write tokens + int m_secret[2]; +}; + + +} } // namespace libtorrent::dht + +#endif // NODE_HPP + diff --git a/include/libtorrent/kademlia/node_entry.hpp b/include/libtorrent/kademlia/node_entry.hpp new file mode 100644 index 000000000..edc5dff80 --- /dev/null +++ b/include/libtorrent/kademlia/node_entry.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef KADEMLIA_NODE_ENTRY_HPP +#define KADEMLIA_NODE_ENTRY_HPP + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/socket.hpp" + +namespace libtorrent { namespace dht +{ + +struct node_entry +{ + node_entry(node_id const& id_, asio::ip::udp::endpoint addr_) + : id(id_) + , addr(addr_) + , fail_count(0) {} + node_entry(asio::ip::udp::endpoint addr_) + : id(0) + , addr(addr_) + , fail_count(0) {} + + node_id id; + udp::endpoint addr; + // the number of times this node has failed to + // respond in a row + int fail_count; +}; + +} } // namespace libtorrent::dht + +#endif + diff --git a/include/libtorrent/kademlia/node_id.hpp b/include/libtorrent/kademlia/node_id.hpp new file mode 100644 index 000000000..eb4d6c539 --- /dev/null +++ b/include/libtorrent/kademlia/node_id.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef NODE_ID_HPP +#define NODE_ID_HPP + +#include +#include + +#include +#include "libtorrent/peer_id.hpp" + +namespace libtorrent { namespace dht +{ + +typedef libtorrent::big_number node_id; + +// returns the distance between the two nodes +// using the kademlia XOR-metric +node_id distance(node_id const& n1, node_id const& n2); + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref); + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// usefult for finding out which bucket a node belongs to +int distance_exp(node_id const& n1, node_id const& n2); + +} } // namespace libtorrent::dht + +#endif // NODE_ID_HPP + diff --git a/include/libtorrent/kademlia/packet_iterator.hpp b/include/libtorrent/kademlia/packet_iterator.hpp new file mode 100644 index 000000000..e906a90bf --- /dev/null +++ b/include/libtorrent/kademlia/packet_iterator.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PACKET_ITERATOR_HPP +#define PACKET_ITERATOR_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +class packet_iterator: public boost::iterator_facade< + packet_iterator, const char, boost::forward_traversal_tag> +{ +public: + typedef std::vector::const_iterator base_iterator; + + packet_iterator() {} + + packet_iterator(std::vector::const_iterator start + , std::vector::const_iterator end + , std::string const& error_msg = "") + : m_base(start) + , m_end(end) + , m_msg(error_msg) + {} + + base_iterator base() const + { return m_base; } + + base_iterator end() const + { return m_end; } + + int left() const { return int(m_end - m_base); } + +private: + friend class boost::iterator_core_access; + + bool equal(packet_iterator const& other) const + { return m_base == other.m_base; } + + void advance(int n) + { + m_base += n; + } + + void increment() + { ++m_base; } + + char const& dereference() const + { + if (m_base == m_end) throw std::runtime_error(m_msg); + return *m_base; + } + + base_iterator m_base; + base_iterator m_end; + std::string m_msg; +}; + +} } // namespace libtorrent::dht + +#endif // PACKET_ITERATOR_HPP + diff --git a/include/libtorrent/kademlia/refresh.hpp b/include/libtorrent/kademlia/refresh.hpp new file mode 100644 index 000000000..8dbb1cfea --- /dev/null +++ b/include/libtorrent/kademlia/refresh.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef REFRESH_050324_HPP +#define REFRESH_050324_HPP + +#include + +#include +#include + +#include + +namespace libtorrent { namespace dht +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(refresh); +#endif + +class routing_table; +class rpc_manager; + +class refresh : public traversal_algorithm +{ +public: + typedef boost::function done_callback; + + template + static void initiate( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback + ); + + void ping_reply(node_id id); + void ping_timeout(node_id id); + +private: + template + refresh( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback + ); + + void done(); + void invoke(node_id const& id, udp::endpoint addr); + + void invoke_pings_or_finish(); + + int m_max_active_pings; + int m_active_pings; + + done_callback m_done_callback; + + std::vector::iterator m_leftover_nodes_iterator; +}; + +template +inline refresh::refresh( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback +) + : traversal_algorithm( + target + , branch_factor + , max_results + , table + , rpc + , first + , last + ) + , m_max_active_pings(max_active_pings) + , m_active_pings(0) + , m_done_callback(callback) +{ + boost::intrusive_ptr self(this); + add_requests(); +} + +template +inline void refresh::initiate( + node_id target + , int branch_factor + , int max_active_pings + , int max_results + , routing_table& table + , InIt first + , InIt last + , rpc_manager& rpc + , done_callback const& callback +) +{ + new refresh( + target + , branch_factor + , max_active_pings + , max_results + , table + , first + , last + , rpc + , callback + ); +} + +} } // namespace libtorrent::dht + +#endif // REFRESH_050324_HPP + diff --git a/include/libtorrent/kademlia/routing_table.hpp b/include/libtorrent/kademlia/routing_table.hpp new file mode 100644 index 000000000..33ba4e42c --- /dev/null +++ b/include/libtorrent/kademlia/routing_table.hpp @@ -0,0 +1,224 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ROUTING_TABLE_HPP +#define ROUTING_TABLE_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace pt = boost::posix_time; + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +//TORRENT_DECLARE_LOG(table); + +typedef std::deque bucket_t; + +// differences in the implementation from the description in +// the paper: +// +// * The routing table tree is not allocated dynamically, there +// are always 160 buckets. +// * Nodes are not marked as being stale, they keep a counter +// that tells how many times in a row they have failed. When +// a new node is to be inserted, the node that has failed +// the most times is replaced. If none of the nodes in the +// bucket has failed, then it is put in the replacement +// cache (just like in the paper). + +class routing_table; + +namespace aux +{ + + // Iterates over a flattened routing_table structure. + class routing_table_iterator + : public boost::iterator_facade< + routing_table_iterator + , node_entry const + , boost::forward_traversal_tag + > + { + public: + routing_table_iterator() + { + } + + private: + friend class libtorrent::dht::routing_table; + friend class boost::iterator_core_access; + + typedef boost::array, 160>::const_iterator + bucket_iterator_t; + + routing_table_iterator( + bucket_iterator_t begin + , bucket_iterator_t end) + : m_bucket_iterator(begin) + , m_bucket_end(end) + , m_iterator(begin != end ? begin->first.begin() : bucket_t::iterator()) + { + if (m_bucket_iterator == m_bucket_end) return; + while (m_iterator == m_bucket_iterator->first.end()) + { + if (++m_bucket_iterator == m_bucket_end) + break; + m_iterator = m_bucket_iterator->first.begin(); + } + } + + bool equal(routing_table_iterator const& other) const + { + return m_bucket_iterator == other.m_bucket_iterator + && (m_iterator == other.m_iterator + || m_bucket_iterator == m_bucket_end); + } + + void increment() + { + assert(m_bucket_iterator != m_bucket_end); + ++m_iterator; + while (m_iterator == m_bucket_iterator->first.end()) + { + if (++m_bucket_iterator == m_bucket_end) + break; + m_iterator = m_bucket_iterator->first.begin(); + } + } + + node_entry const& dereference() const + { + assert(m_bucket_iterator != m_bucket_end); + return *m_iterator; + } + + bucket_iterator_t m_bucket_iterator; + bucket_iterator_t m_bucket_end; + bucket_t::const_iterator m_iterator; + }; + +} // namespace aux + +class routing_table +{ +public: + typedef aux::routing_table_iterator iterator; + typedef iterator const_iterator; + + routing_table(node_id const& id, int bucket_size + , dht_settings const& settings); + + void node_failed(node_id const& id); + + // this function is called every time the node sees + // a sign of a node being alive. This node will either + // be inserted in the k-buckets or be moved to the top + // of its bucket. + bool node_seen(node_id const& id, udp::endpoint addr); + + // returns true if the given bucket is empty but there are nodes + // in a bucket closer to us, or if the bucket is non-empty and + // the time from the last activity is more than 15 minutes + bool should_refresh(int bucket); + + // fills the vector with the count nodes from our buckets that + // are nearest to the given id. + void find_node(node_id const& id, std::vector& l + , bool include_self, int count = 0); + + // returns true if the given node would be placed in a bucket + // that is not full. If the node already exists in the table + // this function returns false + bool need_node(node_id const& id); + + int bucket_size(int bucket) + { + assert(bucket >= 0 && bucket < 160); + return (int)m_buckets[bucket].first.size(); + } + int bucket_size() const { return m_bucket_size; } + + iterator begin() const; + iterator end() const; + + boost::tuple size() const; + + // returns true if there are no working nodes + // in the routing table + bool need_bootstrap() const; + + void replacement_cache(bucket_t& nodes) const; + + // used for debug and monitoring purposes. This will print out + // the state of the routing table to the given stream + void print_state(std::ostream& os) const; + +private: + + // constant called k in paper + int m_bucket_size; + + dht_settings const& m_settings; + + // 160 (k-bucket, replacement cache) pairs + typedef boost::array, 160> table_t; + table_t m_buckets; + // timestamps of the last activity in each bucket + boost::array m_bucket_activity; + node_id m_id; // our own node id + + // this is the lowest bucket index with nodes in it + int m_lowest_active_bucket; +}; + +} } // namespace libtorrent::dht + +#endif // ROUTING_TABLE_HPP + diff --git a/include/libtorrent/kademlia/rpc_manager.hpp b/include/libtorrent/kademlia/rpc_manager.hpp new file mode 100644 index 000000000..405f8d8df --- /dev/null +++ b/include/libtorrent/kademlia/rpc_manager.hpp @@ -0,0 +1,196 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef RPC_MANAGER_HPP +#define RPC_MANAGER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(rpc); +#endif + +typedef std::vector packet_t; + +namespace messages +{ + enum { ping = 0, find_node = 1, get_peers = 2, announce_peer = 3, error = 4 }; + char const* const ids[] = { "ping", "find_node", "get_peers", "announce_peer", "error" }; +} // namespace messages + +struct msg +{ + msg() : reply(false), piggy_backed_ping(false) + , port(0) {} + + // true if this message is a reply + bool reply; + // true if this is a reply with a piggy backed ping + bool piggy_backed_ping; + // the kind if message + int message_id; + // if this is a reply, a copy of the transaction id + // from the request. If it's a request, a transaction + // id that should be sent back in the reply + std::string transaction_id; + // if this packet has a piggy backed ping, this + // is the transaction id of that ping + std::string ping_transaction_id; + // the node id of the process sending the message + node_id id; + // the address of the process sending or receiving + // the message. + udp::endpoint addr; + // if this is a nodes response, these are the nodes + typedef std::vector nodes_t; + nodes_t nodes; + + typedef std::vector peers_t; + peers_t peers; + + // similar to transaction_id but for write operations. + entry write_token; + + // the info has for peer_requests, announce_peer + // and responses + node_id info_hash; + + // port for announce_peer messages + int port; + + // ERROR MESSAGES + int error_code; + std::string error_msg; +}; + +struct observer : boost::noncopyable +{ + observer() + : sent(boost::posix_time::microsec_clock::universal_time()) + {} + + virtual ~observer() {} + + // this two callbacks lets the observer add + // information to the message before it's sent + virtual void send(msg& m) = 0; + + // this is called when a reply is received + virtual void reply(msg const& m) = 0; + + // this is called when no reply has been received within + // some timeout + virtual void timeout() = 0; + + udp::endpoint target_addr; + boost::posix_time::ptime sent; +}; + +class routing_table; + +class rpc_manager +{ +public: + typedef boost::function1 fun; + typedef boost::function1 send_fun; + + rpc_manager(fun const& incoming_fun, node_id const& our_id + , routing_table& table, send_fun const& sf); + ~rpc_manager(); + + // returns true if the node needs a refresh + bool incoming(msg const&); + boost::posix_time::time_duration tick(); + + void invoke(int message_id, udp::endpoint target + , boost::shared_ptr o); + + void reply(msg& m, msg const& reply_to); + void reply_with_ping(msg& m, msg const& reply_to); + +#ifndef NDEBUG + void check_invariant() const; +#endif + +private: + + enum { max_transactions = 1024 }; + unsigned int new_transaction_id(); + void update_oldest_transaction_id(); + + boost::uint32_t calc_connection_id(udp::endpoint addr); + + typedef boost::array, max_transactions> + transactions_t; + transactions_t m_transactions; + + // this is the next transaction id to be used + int m_next_transaction_id; + // this is the oldest transaction id still + // (possibly) in use. This is the transaction + // that will time out first, the one we are + // waiting for to time out + int m_oldest_transaction_id; + + fun m_incoming; + send_fun m_send; + node_id m_our_id; + routing_table& m_table; + boost::posix_time::ptime m_timer; + node_id m_random_number; + // a cache of connection ids + std::map m_connection_id; +}; + +} } // namespace libtorrent::dht + +#endif + + diff --git a/include/libtorrent/kademlia/traversal_algorithm.hpp b/include/libtorrent/kademlia/traversal_algorithm.hpp new file mode 100644 index 000000000..608214f42 --- /dev/null +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRAVERSAL_ALGORITHM_050324_HPP +#define TRAVERSAL_ALGORITHM_050324_HPP + +#include + +#include +#include +#include + +#include +#include +#include + +namespace libtorrent { namespace dht +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DECLARE_LOG(traversal); +#endif + +class rpc_manager; + +// this class may not be instantiated as a stack object +class traversal_algorithm : boost::noncopyable +{ +public: + void traverse(node_id const& id, udp::endpoint addr); + void finished(node_id const& id); + void failed(node_id const& id); + virtual ~traversal_algorithm() {} + +protected: + template + traversal_algorithm( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , InIt start + , InIt end + ); + + void add_request(node_id const& id, udp::endpoint addr); + void add_requests(); + void add_entry(node_id const& id, udp::endpoint addr, unsigned char flags); + + virtual void done() = 0; + virtual void invoke(node_id const& id, udp::endpoint addr) = 0; + + struct result + { + result(node_id const& id, udp::endpoint addr, unsigned char f = 0) + : id(id), addr(addr), flags(f) + {} + + node_id id; + udp::endpoint addr; + enum { queried = 1, initial = 2 }; + unsigned char flags; + }; + + std::vector::iterator last_iterator(); + + friend void intrusive_ptr_add_ref(traversal_algorithm* p) + { + p->m_ref_count++; + } + + friend void intrusive_ptr_release(traversal_algorithm* p) + { + if (--p->m_ref_count == 0) + delete p; + } + + int m_ref_count; + + node_id m_target; + int m_branch_factor; + int m_max_results; + std::vector m_results; + std::set m_failed; + routing_table& m_table; + rpc_manager& m_rpc; + int m_invoke_count; +}; + +template +traversal_algorithm::traversal_algorithm( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , InIt start // <- nodes to initiate traversal with + , InIt end +) + : m_ref_count(0) + , m_target(target) + , m_branch_factor(branch_factor) + , m_max_results(max_results) + , m_table(table) + , m_rpc(rpc) + , m_invoke_count(0) +{ + using boost::bind; + + for (InIt i = start; i != end; ++i) + { + add_entry(i->id, i->addr, result::initial); + } + +} + +} } // namespace libtorrent::dht + +#endif // TRAVERSAL_ALGORITHM_050324_HPP + diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index 990b8c5d0..77fed37ff 100755 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -367,6 +367,7 @@ namespace libtorrent // in the session_settings structure. But it may be lowered // if the peer is known to require a smaller limit (like BitComet). // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. int m_max_out_request_queue; void set_timeout(int s) { m_timeout = s; } diff --git a/include/libtorrent/random_sample.hpp b/include/libtorrent/random_sample.hpp new file mode 100644 index 000000000..741576fd9 --- /dev/null +++ b/include/libtorrent/random_sample.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANDOM_SAMPLE_HPP +#define TORRENT_RANDOM_SAMPLE_HPP + +#include +#include + +namespace libtorrent +{ + + template + inline void random_sample_n(InIter start, InIter end + , OutIter out, Distance n) + { + Distance t = 0; + Distance m = 0; + Distance N = std::distance(start, end); + + assert(N >= n); + + while (m < n) + { + if ((rand() / (RAND_MAX + 1.f)) * (N - t) >= n - m) + { + ++start; + ++t; + } + else + { + *out = *start; + ++out; + ++start; + ++t; + ++m; + } + } + } + +} + +#endif diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp index 8c16d5e97..17196405e 100755 --- a/include/libtorrent/session.hpp +++ b/include/libtorrent/session.hpp @@ -72,6 +72,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/version.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" #if !defined(NDEBUG) && defined(_MSC_VER) # include @@ -309,6 +310,10 @@ namespace libtorrent void second_tick(asio::error const& e); boost::posix_time::ptime m_last_tick; +#ifndef TORRENT_DISABLE_DHT + boost::scoped_ptr m_dht; + dht_settings m_dht_settings; +#endif // the timer used to fire the second_tick deadline_timer m_timer; #ifndef NDEBUG @@ -385,6 +390,14 @@ namespace libtorrent session_status status() const; +#ifndef TORRENT_DISABLE_DHT + void start_dht(entry const& startup_state = entry()); + void stop_dht(); + void set_dht_settings(dht_settings const& settings); + entry dht_state() const; + void add_dht_node(std::pair const& node); +#endif + void enable_extension(extension_index i); void disable_extensions(); diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index 8a264bdd7..c8712626c 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -56,6 +56,7 @@ namespace libtorrent , whole_pieces_threshold(20) , peer_timeout(120) , urlseed_timeout(20) + , urlseed_pipeline_size(5) {} std::string proxy_ip; @@ -136,7 +137,38 @@ namespace libtorrent // this is usually set lower, because web servers are // expected to be more reliable. int urlseed_timeout; + + // controls the pipelining size of url-seeds + int urlseed_pipeline_size; }; + +#ifndef TORRENT_DISABLE_DHT + struct dht_settings + { + dht_settings() + : max_peers_reply(50) + , search_branching(5) + , service_port(6881) + , max_fail_count(20) + {} + + // the maximum number of peers to send in a + // reply to get_peers + int max_peers_reply; + + // the number of simultanous "connections" when + // searching the DHT. + int search_branching; + + // the listen port for the dht. This is a UDP port. + int service_port; + + // the maximum number of times a node can fail + // in a row before it is removed from the table. + int max_fail_count; + }; +#endif + } #endif diff --git a/include/libtorrent/socket.hpp b/include/libtorrent/socket.hpp index c680617d2..b29ebe1ac 100755 --- a/include/libtorrent/socket.hpp +++ b/include/libtorrent/socket.hpp @@ -55,6 +55,8 @@ POSSIBILITY OF SUCH DAMAGE. #undef Protocol #endif +#include "libtorrent/io.hpp" + #ifdef _MSC_VER #pragma warning(pop) #endif @@ -87,6 +89,24 @@ namespace libtorrent using asio::async_write; using asio::deadline_timer; + + namespace detail + { + template + void write_endpoint(Endpoint const& e, OutIt& out) + { + write_uint32(e.address().to_v4().to_ulong(), out); + write_uint16(e.port(), out); + } + + template + Endpoint read_endpoint(InIt& in) + { + unsigned int ip = read_uint32(in); + int port = read_uint16(in); + return Endpoint(address(ip), port); + } + } } #endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index a96a440cc..4c5b0126c 100755 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -322,7 +322,7 @@ namespace libtorrent // this is the asio callback that is called when a name // lookup for a web seed is completed. void on_name_lookup(asio::error const& e, tcp::resolver::iterator i - , int port, std::string url); + , std::string url); // this is called when the torrent has finished. i.e. // all the pieces we have not filtered have been downloaded. @@ -493,6 +493,12 @@ namespace libtorrent // used to resolve the names of web seeds tcp::resolver m_host_resolver; + +#ifndef TORRENT_DISABLE_DHT + deadline_timer m_dht_announce_timer; + void on_dht_announce(asio::error const& e); + void on_dht_announce_response(std::vector const& peers); +#endif // this is the upload and download statistics for the whole torrent. // it's updated from all its peers once every second. diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp index 8d932cb5b..2a7fce751 100755 --- a/include/libtorrent/torrent_info.hpp +++ b/include/libtorrent/torrent_info.hpp @@ -134,6 +134,9 @@ namespace libtorrent void print(std::ostream& os) const; bool is_valid() const { return m_piece_length > 0; } + bool priv() const { return m_private; } + void set_priv(bool v) { m_private = v; } + void convert_file_names(); size_type piece_size(int index) const; @@ -154,6 +157,14 @@ namespace libtorrent const std::string& comment() const { return m_comment; } + // dht nodes to add to the routing table/bootstrap from + typedef std::vector > nodes_t; + + nodes_t const& nodes() const + { return m_nodes; } + + void add_node(std::pair const& node); + void parse_info_section(entry const& e); private: @@ -176,6 +187,8 @@ namespace libtorrent // the list of files that this torrent consists of std::vector m_files; + nodes_t m_nodes; + // the sum of all filesizes size_type m_total_size; @@ -205,6 +218,10 @@ namespace libtorrent // or not. e.g. test/test there's one file and one directory // and they have the same name. bool m_multifile; + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + bool m_private; // contains any non-parsed entries from the info-section // these are kept in order to be able to accurately diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index 8b9bf0145..a45f88b59 100755 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -230,11 +230,12 @@ namespace libtorrent void abort_all_requests(); void remove_request(tracker_connection const*); + bool empty() const; private: typedef boost::recursive_mutex mutex_t; - mutex_t m_mutex; + mutable mutex_t m_mutex; typedef std::list > tracker_connections_t; diff --git a/m4/ax_boost_date-time.m4 b/m4/ax_boost_date-time.m4 index 702c657ef..f4cd1a8e0 100644 --- a/m4/ax_boost_date-time.m4 +++ b/m4/ax_boost_date-time.m4 @@ -36,7 +36,8 @@ if test "$ax_cv_boost_date_time" = yes; then ax_boost_date_time_lib=boost_date_time-$with_boost_date_time fi]) for ax_lib in $ax_date_time_lib $ax_boost_date_time_lib boost_date_time; do - AC_CHECK_LIB($ax_lib, main, [BOOST_DATE_TIME_LIB=$ax_lib break]) + AC_CHECK_LIB($ax_lib, main, [BOOST_DATE_TIME_LIB=$ax_lib +break]) done AC_SUBST(BOOST_DATE_TIME_LIB) fi diff --git a/m4/ax_boost_filesystem.m4 b/m4/ax_boost_filesystem.m4 index f733ac3c9..ffc00f836 100644 --- a/m4/ax_boost_filesystem.m4 +++ b/m4/ax_boost_filesystem.m4 @@ -38,7 +38,8 @@ if test "$ax_cv_boost_filesystem" = yes; then ax_boost_filesystem_lib=boost_filesystem-$with_boost_filesystem fi]) for ax_lib in $ax_filesystem_lib $ax_boost_filesystem_lib boost_filesystem; do - AC_CHECK_LIB($ax_lib, main, [BOOST_FILESYSTEM_LIB=$ax_lib break]) + AC_CHECK_LIB($ax_lib, main, [BOOST_FILESYSTEM_LIB=$ax_lib +break]) done AC_SUBST(BOOST_FILESYSTEM_LIB) fi diff --git a/m4/ax_boost_program_options.m4 b/m4/ax_boost_program_options.m4 index 7e88f6df0..0ac0801cc 100644 --- a/m4/ax_boost_program_options.m4 +++ b/m4/ax_boost_program_options.m4 @@ -34,7 +34,8 @@ if test "$ax_cv_boost_program_options" = yes; then ax_boost_program_options_lib=boost_program_options-$with_boost_program_options fi]) for ax_lib in $ax_program_options_lib $ax_boost_program_options_lib boost_program_options; do - AC_CHECK_LIB($ax_lib, main, [BOOST_PROGRAM_OPTIONS_LIB=$ax_lib break]) + AC_CHECK_LIB($ax_lib, main, [BOOST_PROGRAM_OPTIONS_LIB=$ax_lib +break]) done AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) fi diff --git a/m4/ax_boost_regex.m4 b/m4/ax_boost_regex.m4 index f16f27648..8ff226655 100644 --- a/m4/ax_boost_regex.m4 +++ b/m4/ax_boost_regex.m4 @@ -34,7 +34,8 @@ if test "$ax_cv_boost_regex" = yes; then ax_boost_regex_lib=boost_regex-$with_boost_regex fi]) for ax_lib in $ax_regex_lib $ax_boost_regex_lib boost_regex; do - AC_CHECK_LIB($ax_lib, main, [BOOST_REGEX_LIB=$ax_lib break]) + AC_CHECK_LIB($ax_lib, main, [BOOST_REGEX_LIB=$ax_lib +break]) done AC_SUBST(BOOST_REGEX_LIB) fi diff --git a/m4/ax_boost_thread.m4 b/m4/ax_boost_thread.m4 index 8057920c1..2504b2dbe 100644 --- a/m4/ax_boost_thread.m4 +++ b/m4/ax_boost_thread.m4 @@ -41,7 +41,8 @@ if test "$ax_cv_boost_thread" = yes; then ax_boost_thread_lib=boost_thread-$with_boost_thread fi]) for ax_lib in $ax_thread_lib $ax_boost_thread_lib boost_thread; do - AC_CHECK_LIB($ax_lib, main, [BOOST_THREAD_LIB=$ax_lib break]) + AC_CHECK_LIB($ax_lib, main, [BOOST_THREAD_LIB=$ax_lib +break]) done AC_SUBST(BOOST_THREAD_LIB) fi diff --git a/src/Makefile.am b/src/Makefile.am index 0c13c57ad..5e1c9f6c1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,7 +7,17 @@ piece_picker.cpp policy.cpp session.cpp sha1.cpp stat.cpp \ storage.cpp torrent.cpp torrent_handle.cpp \ torrent_info.cpp tracker_manager.cpp \ http_tracker_connection.cpp udp_tracker_connection.cpp \ -alert.cpp identify_client.cpp ip_filter.cpp file.cpp +alert.cpp identify_client.cpp ip_filter.cpp file.cpp \ +\ +kademlia/closest_nodes.cpp \ +kademlia/dht_tracker.cpp \ +kademlia/find_data.cpp \ +kademlia/node.cpp \ +kademlia/node_id.cpp \ +kademlia/refresh.cpp \ +kademlia/routing_table.cpp \ +kademlia/rpc_manager.cpp \ +kademlia/traversal_algorithm.cpp noinst_HEADERS = \ $(top_srcdir)/include/libtorrent/alert.hpp \ diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 1a6756d35..29e22a8e7 100755 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -272,8 +272,10 @@ namespace libtorrent , i.begin + 8 , 0); +#ifndef TORRENT_DISABLE_DHT // indicate that we support the DHT messages *(i.begin + 7) = 0x01; +#endif // we support extensions *(i.begin + 5) = 0x10; @@ -1352,8 +1354,10 @@ namespace libtorrent // reply with our handshake write_handshake(); write_bitfield(t->pieces()); -// if (m_supports_dht_port) -// write_dht_port(m_ses.dht_port()); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.m_dht) + write_dht_port(m_ses.m_dht_settings.service_port); +#endif } else { diff --git a/src/entry.cpp b/src/entry.cpp index 6e5683ca6..02d86a80f 100755 --- a/src/entry.cpp +++ b/src/entry.cpp @@ -329,7 +329,9 @@ namespace libtorrent { for (int j = 0; j < indent+1; ++j) os << " "; os << "[" << i->first << "]"; - if (i->second.type() != entry::string_t && i->second.type() != entry::int_t) os << "\n"; + if (i->second.type() != entry::string_t + && i->second.type() != entry::int_t) + os << "\n"; else os << " "; i->second.print(os, indent+2); } diff --git a/src/kademlia/closest_nodes.cpp b/src/kademlia/closest_nodes.cpp new file mode 100644 index 000000000..cc83e4fd1 --- /dev/null +++ b/src/kademlia/closest_nodes.cpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +typedef boost::shared_ptr observer_ptr; + +class closest_nodes_observer : public observer +{ +public: + closest_nodes_observer( + boost::intrusive_ptr const& algorithm + , node_id self + , node_id target + ) + : m_algorithm(algorithm) + , m_target(target) + , m_self(self) + {} + + void send(msg& p) + { + p.info_hash = m_target; + } + + void timeout(); + void reply(msg const&); + +private: + boost::intrusive_ptr m_algorithm; + node_id const m_target; + node_id const m_self; +}; + +void closest_nodes_observer::reply(msg const& in) +{ + if (!in.nodes.empty()) + { + for (msg::nodes_t::const_iterator i = in.nodes.begin() + , end(in.nodes.end()); i != end; ++i) + { + m_algorithm->traverse(i->id, i->addr); + } + } + m_algorithm->finished(m_self); +} + +void closest_nodes_observer::timeout() +{ + m_algorithm->failed(m_self); +} + + +closest_nodes::closest_nodes( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) + : traversal_algorithm( + target + , branch_factor + , max_results + , table + , rpc + , table.begin() + , table.end() + ) + , m_done_callback(callback) +{ + boost::intrusive_ptr self(this); + add_requests(); +} + +void closest_nodes::invoke(node_id const& id, udp::endpoint addr) +{ + observer_ptr p(new closest_nodes_observer(this, id, m_target)); + m_rpc.invoke(messages::find_node, addr, p); +} + +void closest_nodes::done() +{ + std::cerr << "[closest_nodes] DONE" << std::endl; + std::vector results; + int result_size = m_table.bucket_size(); + if (result_size > (int)m_results.size()) result_size = (int)m_results.size(); + for (std::vector::iterator i = m_results.begin() + , end(m_results.begin() + result_size); i != end; ++i) + { + results.push_back(node_entry(i->id, i->addr)); + } + m_done_callback(results); +} + +void closest_nodes::initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) +{ + new closest_nodes(target, branch_factor, max_results, table, rpc, callback); +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp new file mode 100644 index 000000000..495207ae8 --- /dev/null +++ b/src/kademlia/dht_tracker.cpp @@ -0,0 +1,772 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" + +using boost::posix_time::ptime; +using boost::posix_time::time_duration; +using boost::posix_time::second_clock; +using boost::posix_time::microsec_clock; +using boost::posix_time::seconds; +using boost::posix_time::minutes; +using boost::posix_time::hours; +using boost::posix_time::milliseconds; +using boost::ref; +using boost::lexical_cast; +using libtorrent::dht::node_impl; +using libtorrent::dht::node_id; +using libtorrent::dht::packet_t; +using libtorrent::dht::msg; +using libtorrent::dht::packet_iterator; +namespace messages = libtorrent::dht::messages; +using namespace libtorrent::detail; + +using asio::ip::udp; +typedef asio::ip::address_v4 address; + +namespace +{ + const int tick_period = 5; // minutes + + struct count_peers + { + int& count; + count_peers(int& c): count(c) {} + void operator()(std::pair const& t) + { + count += std::distance(t.second.peers.begin() + , t.second.peers.end()); + } + }; + + boost::optional read_id(libtorrent::entry const& d) + { + using namespace libtorrent; + using libtorrent::dht::node_id; + + if (d.type() != entry::dictionary_t) return boost::optional(); + entry const* nid = d.find_key("node-id"); + if (!nid + || nid->type() != entry::string_t + || nid->string().length() != 40) + return boost::optional(); + return boost::optional( + boost::lexical_cast(nid->string())); + } +} + + +namespace libtorrent { namespace dht +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_DEFINE_LOG(dht_tracker) +#endif + + // class that puts the networking and the kademlia node in a single + // unit and connecting them together. + dht_tracker::dht_tracker(asio::io_service& d, dht_settings const& settings + , entry const& bootstrap) + : m_demuxer(d) + , m_socket(m_demuxer, udp::endpoint(address(), settings.service_port)) + , m_dht(bind(&dht_tracker::send_packet, this, _1), settings + , read_id(bootstrap)) + , m_buffer(0) + , m_last_refresh(second_clock::universal_time() - hours(1)) + , m_timer(m_demuxer) + , m_second_timer(m_demuxer) + , m_settings(settings) + , m_refresh_bucket(160) + , m_host_resolver(d) + { + using boost::bind; + + m_in_buf[0].resize(1000); + m_in_buf[1].resize(1000); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + m_counter = 0; + std::fill_n(m_replies_bytes_sent, 5, 0); + std::fill_n(m_queries_bytes_received, 5, 0); + std::fill_n(m_replies_sent, 5, 0); + std::fill_n(m_queries_received, 5, 0); + m_announces = 0; + m_failed_announces = 0; + m_total_message_input = 0; + m_ut_message_input = 0; + m_lt_message_input = 0; + m_mp_message_input = 0; + m_gr_message_input = 0; + + // turns on and off individual components' logging + +// rpc_log().enable(false); +// node_log().enable(false); +// traversal_log().enable(false); +// dht_tracker_log.enable(false); + +#endif + std::vector initial_nodes; + + if (bootstrap.type() == entry::dictionary_t) + { + if (entry const* nodes = bootstrap.find_key("nodes")) + { + if (nodes->type() == entry::string_t) + { + std::string const& s = nodes->string(); + std::string::const_iterator in(s.begin()); + while (std::distance(in, s.end()) >= 6) + initial_nodes.push_back(read_endpoint(in)); + } + } + } + + m_dht.bootstrap(initial_nodes, bind(&dht_tracker::on_bootstrap, this)); + + m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] + , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] + , bind(&dht_tracker::on_receive, this, _1, _2)); + m_timer.expires_from_now(seconds(1)); + m_timer.async_wait(bind(&dht_tracker::tick, this, _1)); + + m_second_timer.expires_from_now(seconds(10)); + m_second_timer.async_wait(bind(&dht_tracker::second_tick, this, _1)); + } + + void dht_tracker::second_tick(asio::error const& e) + try + { + if (e) return; + time_duration d = m_dht.tick(); + m_second_timer.expires_from_now(d); + m_second_timer.async_wait(bind(&dht_tracker::second_tick, this, _1)); + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::tick(asio::error const& e) + try + { + if (e) return; + m_timer.expires_from_now(minutes(tick_period)); + m_timer.async_wait(bind(&dht_tracker::tick, this, _1)); + + m_dht.new_write_key(); + m_dht.check_refresh(); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + static bool first = true; + if (first) + { + boost::filesystem::create_directory("libtorrent_logs"); + } + + std::ofstream st("libtorrent_logs/routing_table_state.txt", std::ios_base::trunc); + m_dht.print_state(st); + + // count torrents + int torrents = std::distance(m_dht.begin_data(), m_dht.end_data()); + + // count peers + int peers = 0; + std::for_each(m_dht.begin_data(), m_dht.end_data(), count_peers(peers)); + + std::ofstream pc("libtorrent_logs/dht_stats.log", std::ios_base::app); + if (first) + { + first = false; + using boost::posix_time::to_simple_string; + pc << "\n\n ***** starting log at " << to_simple_string( + second_clock::universal_time()) << " *****\n\n" + << "minute:active nodes:passive nodes" + ":ping replies sent:ping queries recvd:ping" + ":ping replies sent:ping queries recvd:ping" + ":find_node replies bytes sent:find_node queries bytes recv" + ":find_node replies bytes sent:find_node queries bytes recv" + ":get_peers replies sent:get_peers queries recvd:get_peers" + ":get_peers replies bytes sent:get_peers queries bytes recv" + ":announce_peer replies sent:announce_peer queries recvd:announce_peer" + ":announce_peer replies bytes sent:announce_peer queries bytes recv" + ":error replies sent:error queries recvd:error" + ":error replies bytes sent:error queries bytes recv" + ":num torrents:num peers:announces per min" + ":failed announces per min:total msgs per min" + ":ut msgs per min:lt msgs per min:mp msgs per min" + ":gr msgs per min\n\n"; + } + + int active; + int passive; + boost::tie(active, passive) = m_dht.size(); + pc << (m_counter * tick_period) + << "\t" << active + << "\t" << passive; + for (int i = 0; i < 5; ++i) + pc << "\t" << (m_replies_sent[i] / float(tick_period)) + << "\t" << (m_queries_received[i] / float(tick_period)) + << "\t" << (m_replies_bytes_sent[i] / float(tick_period*60)) + << "\t" << (m_queries_bytes_received[i] / float(tick_period*60)); + + pc << "\t" << torrents + << "\t" << peers + << "\t" << m_announces / float(tick_period) + << "\t" << m_failed_announces / float(tick_period) + << "\t" << (m_total_message_input / float(tick_period)) + << "\t" << (m_ut_message_input / float(tick_period)) + << "\t" << (m_lt_message_input / float(tick_period)) + << "\t" << (m_mp_message_input / float(tick_period)) + << "\t" << (m_gr_message_input / float(tick_period)) + << std::endl; + ++m_counter; + std::fill_n(m_replies_bytes_sent, 5, 0); + std::fill_n(m_queries_bytes_received, 5, 0); + std::fill_n(m_replies_sent, 5, 0); + std::fill_n(m_queries_received, 5, 0); + m_announces = 0; + m_failed_announces = 0; + m_total_message_input = 0; + m_ut_message_input = 0; + m_lt_message_input = 0; +#endif + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::announce(sha1_hash const& ih, int listen_port + , boost::function const& + , sha1_hash const&)> f) + { + m_dht.announce(ih, listen_port, f); + } + + // translate bittorrent kademlia message into the generice kademlia message + // used by the library + void dht_tracker::on_receive(asio::error const& error, size_t bytes_transferred) + try + { + int current_buffer = m_buffer; + m_buffer = (m_buffer + 1) & 1; + m_socket.async_receive_from(asio::buffer(&m_in_buf[m_buffer][0] + , m_in_buf[m_buffer].size()), m_remote_endpoint[m_buffer] + , bind(&dht_tracker::on_receive, this, _1, _2)); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + ++m_total_message_input; +#endif + + try + { + using libtorrent::entry; + using libtorrent::bdecode; + + assert(bytes_transferred > 0); + + entry e = bdecode(m_in_buf[current_buffer].begin() + , m_in_buf[current_buffer].end()); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << microsec_clock::universal_time() + << " RECEIVED [" << m_remote_endpoint[current_buffer] + << "]:"; +#endif + + libtorrent::dht::msg m; + m.message_id = 0; + m.addr = m_remote_endpoint[current_buffer]; + m.transaction_id = e["t"].string(); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + try + { + entry const* ver = e.find_key("v"); + if (!ver) throw std::exception(); + + std::string const& client = ver->string(); + if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "UT")) + { + ++m_ut_message_input; + TORRENT_LOG(dht_tracker) << " client: uTorrent"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "LT")) + { + ++m_lt_message_input; + TORRENT_LOG(dht_tracker) << " client: libtorrent"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "MP")) + { + ++m_mp_message_input; + TORRENT_LOG(dht_tracker) << " client: MooPolice"; + } + else if (client.size() > 1 && std::equal(client.begin(), client.begin() + 2, "GR")) + { + ++m_gr_message_input; + TORRENT_LOG(dht_tracker) << " client: GetRight"; + } + else + { + TORRENT_LOG(dht_tracker) << " client: generic"; + } + } + catch (std::exception&) + { + TORRENT_LOG(dht_tracker) << " client: generic"; + }; +#endif + + std::string const& msg_type = e["y"].string(); + + if (msg_type == "r") + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " reply: transaction: " + << m.transaction_id; +#endif + + m.reply = true; + entry const& r = e["r"]; + std::string const& id = r["id"].string(); + if (id.size() != 20) throw std::runtime_error("invalid size of id"); + std::copy(id.begin(), id.end(), m.id.begin()); + + if (entry const* n = r.find_key("values")) + { + m.peers.clear(); + entry::list_type const& peers = n->list(); + for (entry::list_type::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + std::string const& p = i->string(); + if (p.size() < 6) continue; + std::string::const_iterator in = p.begin(); + m.peers.push_back(read_endpoint(in)); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " peers: " << m.peers.size(); +#endif + } + + if (entry const* n = r.find_key("nodes")) + { + m.nodes.clear(); + std::string const& nodes = n->string(); + std::string::const_iterator i = nodes.begin(); + std::string::const_iterator end = nodes.end(); + + while (std::distance(i, end) >= 26) + { + node_id id; + std::copy(i, i + 20, id.begin()); + i += 20; + m.nodes.push_back(libtorrent::dht::node_entry( + id, read_endpoint(i))); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " nodes: " << m.nodes.size(); +#endif + } + + entry const* token = r.find_key("token"); + if (token) m.write_token = *token; + } + else if (msg_type == "q") + { + m.reply = false; + entry const& a = e["a"]; + std::string const& id = a["id"].string(); + if (id.size() != 20) throw std::runtime_error("invalid size of id"); + std::copy(id.begin(), id.end(), m.id.begin()); + + std::string request_kind(e["q"].string()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " query: " << request_kind; +#endif + + if (request_kind == "ping") + { + m.message_id = libtorrent::dht::messages::ping; + } + else if (request_kind == "find_node") + { + std::string const& target = a["target"].string(); + if (target.size() != 20) throw std::runtime_error("invalid size of target id"); + std::copy(target.begin(), target.end(), m.info_hash.begin()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " target: " + << boost::lexical_cast(m.info_hash); +#endif + + m.message_id = libtorrent::dht::messages::find_node; + } + else if (request_kind == "get_peers") + { + std::string const& info_hash = a["info_hash"].string(); + if (info_hash.size() != 20) throw std::runtime_error("invalid size of info-hash"); + std::copy(info_hash.begin(), info_hash.end(), m.info_hash.begin()); + m.message_id = libtorrent::dht::messages::get_peers; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " info_hash: " + << boost::lexical_cast(m.info_hash); +#endif + } + else if (request_kind == "announce_peer") + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + ++m_announces; +#endif + std::string const& info_hash = a["info_hash"].string(); + if (info_hash.size() != 20) + throw std::runtime_error("invalid size of info-hash"); + std::copy(info_hash.begin(), info_hash.end(), m.info_hash.begin()); + m.port = a["port"].integer(); + m.write_token = a["token"]; + m.message_id = libtorrent::dht::messages::announce_peer; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " info_hash: " + << boost::lexical_cast(m.info_hash); + TORRENT_LOG(dht_tracker) << " port: " << m.port; + + if (!m_dht.verify_token(m)) + ++m_failed_announces; +#endif + } + else + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " *** UNSUPPORTED REQUEST *** : " + << request_kind; +#endif + throw std::runtime_error("unsupported request: " + request_kind); + } + } + else if (msg_type == "e") + { + entry::list_type const& list = e["e"].list(); + m.message_id = messages::error; + m.error_msg = list.back().string(); + m.error_code = list.front().integer(); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " error: " << m.error_code << " " + << m.error_msg; +#endif + } + else + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " *** UNSUPPORTED MESSAGE TYPE *** : " + << msg_type; +#endif + throw std::runtime_error("unsupported message type: " + msg_type); + } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + if (!m.reply) + { + ++m_queries_received[m.message_id]; + m_queries_bytes_received[m.message_id] += int(bytes_transferred); + } + TORRENT_LOG(dht_tracker) << e; +#endif + + m_dht.incoming(m); + } + catch (std::exception& e) + { + std::cerr << "invalid incoming packet: " << e.what(); + } + } + catch (std::exception& e) + { + assert(false); + }; + + entry dht_tracker::state() const + { + entry ret(entry::dictionary_t); + { + std::string nodes; + std::back_insert_iterator out(nodes); + for (node_impl::iterator i(m_dht.begin()) + , end(m_dht.end()); i != end; ++i) + write_endpoint(i->addr, out); + bucket_t cache; + m_dht.replacement_cache(cache); + for (bucket_t::iterator i(cache.begin()) + , end(cache.end()); i != end; ++i) + write_endpoint(i->addr, out); + if (!nodes.empty()) + ret["nodes"] = nodes; + } + + ret["node-id"] = boost::lexical_cast(m_dht.nid()); + return ret; + } + + void dht_tracker::add_node(udp::endpoint node) + { + m_dht.add_node(node); + } + + void dht_tracker::add_node(std::pair const& node) + { + udp::resolver::query q(node.first, lexical_cast(node.second)); + m_host_resolver.async_resolve(q, bind(&dht_tracker::on_name_lookup + , this, _1, _2)); + } + + void dht_tracker::on_name_lookup(asio::error const& e, udp::resolver::iterator host) + try + { + if (e || host == udp::resolver::iterator()) return; + add_node(host->endpoint()); + } + catch (std::exception&) + { + assert(false); + }; + + void dht_tracker::on_bootstrap() + {} + + void dht_tracker::send_packet(msg const& m) + { + using libtorrent::bencode; + using libtorrent::entry; + entry e(entry::dictionary_t); + e["t"] = m.transaction_id; + std::string version_str("LT "); + std::string::iterator i = version_str.begin() + 2; + detail::write_uint8(LIBTORRENT_VERSION_MAJOR, i); + detail::write_uint8(LIBTORRENT_VERSION_MINOR, i); + e["v"] = version_str; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << microsec_clock::universal_time() + << " SENDING [" << m.addr << "]:"; + TORRENT_LOG(dht_tracker) << " transaction: " << m.transaction_id; +// e.print(std::cerr); +#endif + + if (m.message_id == messages::error) + { + assert(m.reply); + e["y"] = "e"; + entry error_list(entry::list_t); + error_list.list().push_back(entry(m.error_code)); + error_list.list().push_back(entry(m.error_msg)); + e["e"] = error_list; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " error: " << m.error_code << " " + << m.error_msg; +#endif + } + else if (m.reply) + { + e["y"] = "r"; + e["r"] = entry(entry::dictionary_t); + entry& r = e["r"]; + r["id"] = std::string(m.id.begin(), m.id.end()); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " reply: " + << messages::ids[m.message_id]; +#endif + + if (m.write_token.type() != entry::undefined_t) + r["token"] = m.write_token; + + switch (m.message_id) + { + case messages::ping: + break; + case messages::find_node: + { + r["nodes"] = entry(entry::string_t); + entry& n = r["nodes"]; + std::back_insert_iterator out(n.string()); + for (msg::nodes_t::const_iterator i = m.nodes.begin() + , end(m.nodes.end()); i != end; ++i) + { + std::copy(i->id.begin(), i->id.end(), out); + write_endpoint(i->addr, out); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " nodes: " << m.nodes.size(); +#endif + break; + } + case messages::get_peers: + { + if (m.peers.empty()) + { + r["nodes"] = entry(entry::string_t); + entry& n = r["nodes"]; + std::back_insert_iterator out(n.string()); + for (msg::nodes_t::const_iterator i = m.nodes.begin() + , end(m.nodes.end()); i != end; ++i) + { + std::copy(i->id.begin(), i->id.end(), out); + write_endpoint(i->addr, out); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " nodes: " << m.nodes.size(); +#endif + } + else + { + r["values"] = entry(entry::list_t); + entry& p = r["values"]; + std::string endpoint; + endpoint.resize(6); + for (msg::peers_t::const_iterator i = m.peers.begin() + , end(m.peers.end()); i != end; ++i) + { + std::string::iterator out = endpoint.begin(); + write_endpoint(*i, out); + p.list().push_back(entry(endpoint)); + } +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " peers: " << m.peers.size(); +#endif + } + break; + } + + case messages::announce_peer: + break; + break; + } + } + else + { + e["y"] = "q"; + e["a"] = entry(entry::dictionary_t); + entry& a = e["a"]; + a["id"] = std::string(m.id.begin(), m.id.end()); + + if (m.write_token.type() != entry::undefined_t) + a["token"] = m.write_token; + assert(m.message_id <= messages::error); + e["q"] = messages::ids[m.message_id]; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " query: " + << messages::ids[m.message_id]; +#endif + + switch (m.message_id) + { + case messages::find_node: + { + a["target"] = std::string(m.info_hash.begin(), m.info_hash.end()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " target: " + << boost::lexical_cast(m.info_hash); +#endif + break; + } + case messages::get_peers: + { + a["info_hash"] = std::string(m.info_hash.begin(), m.info_hash.end()); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " info_hash: " + << boost::lexical_cast(m.info_hash); +#endif + break; + } + case messages::announce_peer: + a["port"] = m_settings.service_port; + a["info_hash"] = boost::lexical_cast(m.info_hash); + a["token"] = m.write_token; +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(dht_tracker) << " port: " + << m_settings.service_port + << " info_hash: " << boost::lexical_cast(m.info_hash); +#endif + break; + default: break; + } + + } + + m_send_buf.clear(); + bencode(std::back_inserter(m_send_buf), e); + m_socket.send_to(asio::buffer(&m_send_buf[0] + , (int)m_send_buf.size()), m.addr); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + if (m.reply) + { + ++m_replies_sent[m.message_id]; + m_replies_bytes_sent[m.message_id] += int(m_send_buf.size()); + } + TORRENT_LOG(dht_tracker) << e; +#endif + + if (!m.piggy_backed_ping) return; + + msg pm; + pm.reply = false; + pm.piggy_backed_ping = false; + pm.message_id = messages::ping; + pm.transaction_id = m.ping_transaction_id; + pm.id = m.id; + pm.addr = m.addr; + + send_packet(pm); + } + +}} + diff --git a/src/kademlia/find_data.cpp b/src/kademlia/find_data.cpp new file mode 100644 index 000000000..e1e09925b --- /dev/null +++ b/src/kademlia/find_data.cpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +typedef boost::shared_ptr observer_ptr; + +class find_data_observer : public observer +{ +public: + find_data_observer( + boost::intrusive_ptr const& algorithm + , node_id self + , node_id target) + : m_algorithm(algorithm) + , m_target(target) + , m_self(self) + {} + + void send(msg& m) + { + m.reply = false; + m.message_id = messages::get_peers; + m.info_hash = m_target; + } + + void timeout(); + void reply(msg const&); + +private: + boost::intrusive_ptr m_algorithm; + node_id const m_target; + node_id const m_self; +}; + +void find_data_observer::reply(msg const& m) +{ + if (!m.peers.empty()) + { + m_algorithm->got_data(&m); + } + else + { + for (msg::nodes_t::const_iterator i = m.nodes.begin() + , end(m.nodes.end()); i != end; ++i) + { + m_algorithm->traverse(i->id, i->addr); + } + } + m_algorithm->finished(m_self); +} + +void find_data_observer::timeout() +{ + m_algorithm->failed(m_self); +} + + +find_data::find_data( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) + : traversal_algorithm( + target + , branch_factor + , max_results + , table + , rpc + , table.begin() + , table.end() + ) + , m_done_callback(callback) + , m_done(false) +{ + boost::intrusive_ptr self(this); + add_requests(); +} + +void find_data::invoke(node_id const& id, asio::ip::udp::endpoint addr) +{ + if (m_done) + { + m_invoke_count = -1; + return; + } + + observer_ptr p(new find_data_observer(this, id, m_target)); + m_rpc.invoke(messages::get_peers, addr, p); +} + +void find_data::got_data(msg const* m) +{ + m_done = true; + m_done_callback(m); +} + +void find_data::done() +{ + if (m_invoke_count != 0) return; + if (!m_done) m_done_callback(0); +} + +void find_data::initiate( + node_id target + , int branch_factor + , int max_results + , routing_table& table + , rpc_manager& rpc + , done_callback const& callback +) +{ + std::cerr << "find_data::initiate, key: " << target << "\n"; + new find_data(target, branch_factor, max_results, table, rpc, callback); +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp new file mode 100644 index 000000000..d20006b57 --- /dev/null +++ b/src/kademlia/node.cpp @@ -0,0 +1,501 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "libtorrent/io.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/random_sample.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/packet_iterator.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/node.hpp" + +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/closest_nodes.hpp" +#include "libtorrent/kademlia/find_data.hpp" + +using boost::bind; +using boost::posix_time::second_clock; +using boost::posix_time::seconds; +using boost::posix_time::minutes; +using boost::posix_time::ptime; +using boost::posix_time::time_duration; + +namespace libtorrent { namespace dht +{ + +typedef boost::shared_ptr observer_ptr; + +// TODO: configurable +enum { announce_interval = 30 }; + +using asio::ip::udp; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(node) +#endif + +node_id generate_id() +{ + char random[20]; + std::srand(std::time(0)); + std::generate(random, random + 20, &std::rand); + + hasher h; + h.update(random, 20); + return h.final(); +} + +// remove peers that have timed out +void purge_peers(std::set& peers) +{ + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end;) + { + // the peer has timed out + if (i->added + minutes(int(announce_interval * 1.5f)) < second_clock::universal_time()) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(node) << "peer timed out at: " << i->addr.address(); +#endif + peers.erase(i++); + } + else + ++i; + } +} + +void nop() {} + +node_impl::node_impl(boost::function const& f + , dht_settings const& settings, boost::optional node_id) + : m_settings(settings) + , m_id(node_id ? *node_id : generate_id()) + , m_table(m_id, 8, settings) + , m_rpc(bind(&node_impl::incoming_request, this, _1) + , m_id, m_table, f) + , m_last_tracker_tick(boost::posix_time::second_clock::universal_time()) +{ + m_secret[0] = std::rand(); + m_secret[1] = std::rand(); +} + +bool node_impl::verify_token(msg const& m) +{ + if (m.write_token.type() != entry::string_t) + return false; + std::string const& token = m.write_token.string(); + if (token.length() != 4) return false; + + hasher h1; + std::string address = m.addr.address().to_string(); + h1.update(&address[0], address.length()); + h1.update((char*)&m_secret[0], sizeof(m_secret[0])); + h1.update((char*)&m.info_hash[0], sha1_hash::size); + + sha1_hash h = h1.final(); + if (std::equal(token.begin(), token.end(), (signed char*)&h[0])) + return true; + + hasher h2; + h2.update(&address[0], address.length()); + h2.update((char*)&m_secret[1], sizeof(m_secret[1])); + h = h2.final(); + if (std::equal(token.begin(), token.end(), (signed char*)&h[0])) + return true; + return false; +} + +entry node_impl::generate_token(msg const& m) +{ + std::string token; + token.resize(4); + hasher h; + std::string address = m.addr.address().to_string(); + h.update(&address[0], address.length()); + h.update((char*)&m_secret[0], sizeof(m_secret[0])); + h.update((char*)&m.info_hash[0], sha1_hash::size); + + sha1_hash hash = h.final(); + std::copy(hash.begin(), hash.begin() + 4, (signed char*)&token[0]); + return entry(token); +} + +void node_impl::check_refresh() +{ + for (int i = 0; i < 160; ++i) + { + if (m_table.should_refresh(i)) + { + refresh_bucket(i); + } + } +} + +void node_impl::refresh(node_id const& id + , boost::function0 f) +{ + // use the 'bucket size' closest nodes + // to start the refresh with + std::vector start; + start.reserve(m_table.bucket_size()); + m_table.find_node(id, start, false); + refresh::initiate(id, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, f); +} + +void node_impl::bootstrap(std::vector const& nodes + , boost::function0 f) +{ + std::vector start; + start.reserve(nodes.size()); + std::copy(nodes.begin(), nodes.end(), std::back_inserter(start)); + refresh::initiate(m_id, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, f); +} + +void node_impl::refresh() +{ + std::vector start; + start.reserve(m_table.size().get<0>()); + std::copy(m_table.begin(), m_table.end(), std::back_inserter(start)); + + refresh::initiate(m_id, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, bind(&nop)); +} + +int node_impl::bucket_size(int bucket) +{ + return m_table.bucket_size(bucket); +} + +void node_impl::new_write_key() +{ + m_secret[1] = m_secret[0]; + m_secret[0] = std::rand(); +} + +void node_impl::refresh_bucket(int bucket) +{ + assert(bucket >= 0 && bucket < 160); + + // generate a random node_id within the given bucket + node_id target = generate_id(); + int num_bits = 160 - bucket; + node_id mask(0); + for (int i = 0; i < num_bits; ++i) + { + int byte = i / 8; + mask[byte] |= 0x80 >> (i % 8); + } + + node_id root = m_id; + root &= mask; + target &= ~mask; + target |= root; + + // make sure this is in another subtree than m_id + // clear the (num_bits - 1) bit and then set it to the + // inverse of m_id's corresponding bit. + target[(num_bits - 1) / 8] &= ~(0x80 >> ((num_bits - 1) % 8)); + target[(num_bits - 1) / 8] |= + (~(m_id[(num_bits - 1) / 8])) & (0x80 >> ((num_bits - 1) % 8)); + + int new_bucket = distance_exp(m_id, target); + assert(new_bucket == bucket); + + std::vector start; + start.reserve(m_table.bucket_size()); + m_table.find_node(target, start, false, m_table.bucket_size()); + + refresh::initiate(target, m_settings.search_branching, 10, m_table.bucket_size() + , m_table, start.begin(), start.end(), m_rpc, bind(&nop)); +} + + +void node_impl::incoming(msg const& m) +{ + if (m_rpc.incoming(m)) + { + refresh(); + } +} + +namespace +{ + + class announce_observer : public observer + { + public: + announce_observer(sha1_hash const& info_hash, int listen_port + , entry const& write_token) + : m_info_hash(info_hash) + , m_listen_port(listen_port) + , m_token(write_token) + {} + + void send(msg& m) + { + m.port = m_listen_port; + m.info_hash = m_info_hash; + m.write_token = m_token; + } + + void timeout() {} + void reply(msg const&) {} + + private: + sha1_hash m_info_hash; + int m_listen_port; + entry m_token; + }; + + class get_peers_observer : public observer + { + public: + get_peers_observer(sha1_hash const& info_hash, int listen_port + , rpc_manager& rpc + , boost::function const&, sha1_hash const&)> f) + : m_info_hash(info_hash) + , m_listen_port(listen_port) + , m_rpc(rpc) + , m_fun(f) + {} + + void send(msg& m) + { + m.port = m_listen_port; + m.info_hash = m_info_hash; + } + + void timeout() {} + void reply(msg const& r) + { + m_rpc.invoke(messages::announce_peer, r.addr + , boost::shared_ptr( + new announce_observer(m_info_hash, m_listen_port, r.write_token))); + m_fun(r.peers, m_info_hash); + } + + private: + sha1_hash m_info_hash; + int m_listen_port; + rpc_manager& m_rpc; + boost::function const&, sha1_hash const&)> m_fun; + }; + + + void announce_fun(std::vector const& v, rpc_manager& rpc + , int listen_port, sha1_hash const& ih + , boost::function const&, sha1_hash const&)> f) + { + bool nodes = false; + // only store on the first k nodes + for (std::vector::const_iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + rpc.invoke(messages::get_peers, i->addr, boost::shared_ptr( + new get_peers_observer(ih, listen_port, rpc, f))); + nodes = true; + } + } + +} + +namespace +{ + struct dummy_observer : observer + { + virtual void reply(msg const&) {} + virtual void timeout() {} + virtual void send(msg&) {} + }; +} + +void node_impl::add_node(udp::endpoint node) +{ + // ping the node, and if we get a reply, it + // will be added to the routing table + observer_ptr p(new dummy_observer()); + m_rpc.invoke(messages::ping, node, p); +} + +void node_impl::announce(sha1_hash const& info_hash, int listen_port + , boost::function const&, sha1_hash const&)> f) +{ + // search for nodes with ids close to id, and then invoke the + // get_peers and then announce_peer rpc on them. + closest_nodes::initiate(info_hash, m_settings.search_branching + , m_table.bucket_size(), m_table, m_rpc + , boost::bind(&announce_fun, _1, boost::ref(m_rpc), listen_port + , info_hash, f)); +} + +time_duration node_impl::tick() +{ + time_duration d = m_rpc.tick(); + + ptime now(second_clock::universal_time()); + if (now - m_last_tracker_tick < minutes(10)) return d; + m_last_tracker_tick = now; + + // look through all peers and see if any have timed out + for (data_iterator i = begin_data(), end(end_data()); i != end;) + { + torrent_entry& t = i->second; + node_id const& key = i->first; + ++i; + purge_peers(t.peers); + + // if there are no more peers, remove the entry altogether + if (t.peers.empty()) + { + table_t::iterator i = m_map.find(key); + if (i != m_map.end()) m_map.erase(i); + } + } + return d; +} + +void node_impl::on_announce(msg const& m, msg& reply) +{ + if (!verify_token(m)) + { + reply.message_id = messages::error; + reply.error_code = 203; + reply.error_msg = "Incorrect write token in announce_peer message"; + return; + } + + torrent_entry& v = m_map[m.info_hash]; + peer_entry e; + e.addr = tcp::endpoint(m.addr.address(), m.addr.port()); + e.added = second_clock::universal_time(); + std::set::iterator i = v.peers.find(e); + if (i != v.peers.end()) v.peers.erase(i++); + v.peers.insert(i, e); +} + +namespace +{ + tcp::endpoint get_endpoint(peer_entry const& p) + { + return p.addr; + } +} + +bool node_impl::on_find(msg const& m, std::vector& peers) const +{ + table_t::const_iterator i = m_map.find(m.info_hash); + if (i == m_map.end()) return false; + + torrent_entry const& v = i->second; + + int num = (std::min)((int)v.peers.size(), m_settings.max_peers_reply); + peers.clear(); + peers.reserve(num); + random_sample_n(boost::make_transform_iterator(v.peers.begin(), &get_endpoint) + , boost::make_transform_iterator(v.peers.end(), &get_endpoint) + , std::back_inserter(peers), num); + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + TORRENT_LOG(node) << " " << *i; + } +#endif + return true; +} + +void node_impl::incoming_request(msg const& m) +{ + msg reply; + switch (m.message_id) + { + case messages::ping: + break; + case messages::get_peers: + { + reply.info_hash = m.info_hash; + reply.write_token = generate_token(m); + + if (!on_find(m, reply.peers)) + { + // we don't have any peers for this info_hash, + // return nodes instead + m_table.find_node(m.info_hash, reply.nodes, false); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + for (std::vector::iterator i = reply.nodes.begin() + , end(reply.nodes.end()); i != end; ++i) + { + TORRENT_LOG(node) << " " << i->id << " " << i->addr; + } +#endif + } + } + break; + case messages::find_node: + { + reply.info_hash = m.info_hash; + + m_table.find_node(m.info_hash, reply.nodes, false); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + for (std::vector::iterator i = reply.nodes.begin() + , end(reply.nodes.end()); i != end; ++i) + { + TORRENT_LOG(node) << " " << i->id << " " << i->addr; + } +#endif + } + break; + case messages::announce_peer: + { + on_announce(m, reply); + } + break; + }; + + if (m_table.need_node(m.id)) + m_rpc.reply_with_ping(reply, m); + else + m_rpc.reply(reply, m); +} + + +} } // namespace libtorrent::dht diff --git a/src/kademlia/node_id.cpp b/src/kademlia/node_id.cpp new file mode 100644 index 000000000..d435846d2 --- /dev/null +++ b/src/kademlia/node_id.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include "libtorrent/kademlia/node_id.hpp" + +using boost::bind; + +namespace libtorrent { namespace dht +{ + +// returns the distance between the two nodes +// using the kademlia XOR-metric +node_id distance(node_id const& n1, node_id const& n2) +{ + node_id ret; + node_id::iterator k = ret.begin(); + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , end(n1.end()); i != end; ++i, ++j, ++k) + { + *k = *i ^ *j; + } + return ret; +} + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref) +{ + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , k = ref.begin(), end(n1.end()); i != end; ++i, ++j, ++k) + { + boost::uint8_t lhs = (*i ^ *k); + boost::uint8_t rhs = (*j ^ *k); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; +} + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// useful for finding out which bucket a node belongs to +int distance_exp(node_id const& n1, node_id const& n2) +{ + int byte = node_id::size - 1; + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , end(n1.end()); i != end; ++i, ++j, --byte) + { + assert(byte >= 0); + boost::uint8_t t = *i ^ *j; + if (t == 0) continue; + // we have found the first non-zero byte + // return the bit-number of the first bit + // that differs + int bit = byte * 8; + for (int b = 7; b > 0; --b) + if (t >= (1 << b)) return bit + b; + return bit; + } + + return 0; +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/refresh.cpp b/src/kademlia/refresh.cpp new file mode 100644 index 000000000..903af73dd --- /dev/null +++ b/src/kademlia/refresh.cpp @@ -0,0 +1,188 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include + +#include + +using boost::bind; + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; + +TORRENT_DEFINE_LOG(refresh) + +typedef boost::shared_ptr observer_ptr; + +class refresh_observer : public observer +{ +public: + refresh_observer( + boost::intrusive_ptr const& algorithm + , node_id self + , node_id target + ) + : m_target(target) + , m_self(self) + , m_algorithm(algorithm) + {} + + void send(msg& m) + { + m.info_hash = m_target; + } + + void timeout(); + void reply(msg const& m); + +private: + node_id const m_target; + node_id const m_self; + boost::intrusive_ptr m_algorithm; +}; + +void refresh_observer::reply(msg const& in) +{ + if (!in.nodes.empty()) + { + for (msg::nodes_t::const_iterator i = in.nodes.begin() + , end(in.nodes.end()); i != end; ++i) + { + m_algorithm->traverse(i->id, i->addr); + } + } + m_algorithm->finished(m_self); +} + +void refresh_observer::timeout() +{ + m_algorithm->failed(m_self); +} + +class ping_observer : public observer +{ +public: + ping_observer( + boost::intrusive_ptr const& algorithm + , node_id self + ) + : m_self(self) + , m_algorithm(algorithm) + {} + + void send(msg& p) {} + void timeout(); + void reply(msg const& m); + +private: + node_id const m_self; + boost::intrusive_ptr m_algorithm; +}; + +void ping_observer::reply(msg const& m) +{ + m_algorithm->ping_reply(m_self); +} + +void ping_observer::timeout() +{ + m_algorithm->ping_timeout(m_self); +} + +void refresh::invoke(node_id const& id, udp::endpoint addr) +{ + observer_ptr p(new refresh_observer( + this + , id + , m_target + )); + + m_rpc.invoke(messages::find_node, addr, p); +} + +void refresh::done() +{ + m_leftover_nodes_iterator = (int)m_results.size() > m_max_results ? + m_results.begin() + m_max_results : m_results.end(); + + invoke_pings_or_finish(); +} + +void refresh::ping_reply(node_id id) +{ + m_active_pings--; + invoke_pings_or_finish(); +} + +void refresh::ping_timeout(node_id id) +{ + m_active_pings--; + invoke_pings_or_finish(); +} + +void refresh::invoke_pings_or_finish() +{ + while (m_active_pings < m_max_active_pings) + { + if (m_leftover_nodes_iterator == m_results.end()) break; + + result const& node = *m_leftover_nodes_iterator; + + // Skip initial nodes + if (node.flags & result::initial) + { + ++m_leftover_nodes_iterator; + continue; + } + + observer_ptr p(new ping_observer(this, node.id)); + + m_rpc.invoke(messages::ping, node.addr, p); + ++m_active_pings; + ++m_leftover_nodes_iterator; + } + + if (m_active_pings == 0) + { + m_done_callback(); + } +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/routing_table.cpp b/src/kademlia/routing_table.cpp new file mode 100644 index 000000000..1d908c7e1 --- /dev/null +++ b/src/kademlia/routing_table.cpp @@ -0,0 +1,417 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/session_settings.hpp" + +using boost::bind; +using boost::uint8_t; + +using boost::posix_time::second_clock; +using boost::posix_time::minutes; +using boost::posix_time::seconds; +using boost::posix_time::hours; + +namespace pt = boost::posix_time; + +namespace libtorrent { namespace dht +{ + +using asio::ip::udp; +typedef asio::ip::address_v4 address; + +routing_table::routing_table(node_id const& id, int bucket_size + , dht_settings const& settings) + : m_bucket_size(bucket_size) + , m_settings(settings) + , m_id(id) + , m_lowest_active_bucket(160) +{ + std::fill(m_bucket_activity.begin(), m_bucket_activity.end() + , second_clock::universal_time() - hours(1)); +} + +boost::tuple routing_table::size() const +{ + int nodes = 0; + int replacements = 0; + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + nodes += i->first.size(); + replacements += i->second.size(); + } + return boost::make_tuple(nodes, replacements); +} + +void routing_table::print_state(std::ostream& os) const +{ + os << "kademlia routing table state\n" + << "bucket_size: " << m_bucket_size << "\n" + << "node_id: " << m_id << "\n\n"; + + os << "number of nodes per bucket:\n" + "live\n"; + for (int k = 0; k < 8; ++k) + { + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + os << (int(i->first.size()) > (7 - k) ? "|" : " "); + } + os << "\n"; + } + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + os << "+"; + } + os << "\n"; + for (int k = 0; k < 8; ++k) + { + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + os << (int(i->second.size()) > k ? "|" : " "); + } + os << "\n"; + } + os << "cached\n-----------\n"; + + os << "nodes:\n"; + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i) + { + int bucket_index = int(i - m_buckets.begin()); + os << "bucket " << bucket_index << " " + << to_simple_string(m_bucket_activity[bucket_index]) + << " " << (bucket_index >= m_lowest_active_bucket?"active":"inactive") + << "\n"; + for (bucket_t::const_iterator j = i->first.begin() + , end(i->first.end()); j != end; ++j) + { + os << "ip: " << j->addr << " fails: " << j->fail_count + << " id: " << j->id << "\n"; + } + } +} + +bool routing_table::should_refresh(int bucket) +{ + assert(bucket < 160); + assert(bucket >= 0); + // lower than or equal to since a refresh of bucket 0 will + // effectively refresh the lowest active bucket as well + if (bucket <= m_lowest_active_bucket && bucket > 0) return false; + if (m_bucket_activity[bucket] + minutes(15) + > second_clock::universal_time()) + return false; + return true; +} + +void routing_table::replacement_cache(bucket_t& nodes) const +{ + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + std::copy(i->second.begin(), i->second.end() + , std::back_inserter(nodes)); + } +} + +bool routing_table::need_node(node_id const& id) +{ + int bucket_index = distance_exp(m_id, id); + assert(bucket_index < (int)m_buckets.size()); + assert(bucket_index >= 0); + bucket_t& b = m_buckets[bucket_index].first; + bucket_t& rb = m_buckets[bucket_index].second; + + // if the replacement cache is full, we don't + // need another node. The table is fine the + // way it is. + if ((int)rb.size() >= m_bucket_size) return false; + + // if the node already exists, we don't need it + if (std::find_if(b.begin(), b.end(), bind(std::equal_to() + , bind(&node_entry::id, _1), id)) != b.end()) return false; + + if (std::find_if(rb.begin(), rb.end(), bind(std::equal_to() + , bind(&node_entry::id, _1), id)) != rb.end()) return false; + + return true; +} + +void routing_table::node_failed(node_id const& id) +{ + int bucket_index = distance_exp(m_id, id); + assert(bucket_index < (int)m_buckets.size()); + assert(bucket_index >= 0); + bucket_t& b = m_buckets[bucket_index].first; + bucket_t& rb = m_buckets[bucket_index].second; + + bucket_t::iterator i = std::find_if(b.begin(), b.end() + , bind(std::equal_to() + , bind(&node_entry::id, _1), id)); + + if (i == b.end()) return; + + if (rb.empty()) + { + ++i->fail_count; + if (i->fail_count >= m_settings.max_fail_count) + { + b.erase(i); + assert(m_lowest_active_bucket <= bucket_index); + while (m_buckets[m_lowest_active_bucket].first.empty() + && m_lowest_active_bucket < 160) + { + ++m_lowest_active_bucket; + } + } + return; + } + + b.erase(i); + b.push_back(rb.back()); + rb.erase(rb.end() - 1); +} + +// this function is called every time the node sees +// a sign of a node being alive. This node will either +// be inserted in the k-buckets or be moved to the top +// of its bucket. +// the return value indicates if the table needs a refresh. +// if true, the node should refresh the table (i.e. do a find_node +// on its own id) +bool routing_table::node_seen(node_id const& id, udp::endpoint addr) +{ + int bucket_index = distance_exp(m_id, id); + assert(bucket_index < (int)m_buckets.size()); + assert(bucket_index >= 0); + bucket_t& b = m_buckets[bucket_index].first; + + bucket_t::iterator i = std::find_if(b.begin(), b.end() + , bind(std::equal_to() + , bind(&node_entry::id, _1), id)); + + bool ret = need_bootstrap(); + + m_bucket_activity[bucket_index] = second_clock::universal_time(); + + if (i != b.end()) + { + // TODO: what do we do if we see a node with + // the same id as a node at a different address? +// assert(i->addr == addr); + + // we already have the node in our bucket + // just move it to the back since it was + // the last node we had any contact with + // in this bucket + b.erase(i); + b.push_back(node_entry(id, addr)); +// TORRENT_LOG(table) << "replacing node: " << id << " " << addr; + return ret; + } + + // if the node was not present in our list + // we will only insert it if there is room + // for it, or if some of our nodes have gone + // offline + if ((int)b.size() < m_bucket_size) + { + b.push_back(node_entry(id, addr)); + if (bucket_index < m_lowest_active_bucket) + m_lowest_active_bucket = bucket_index; +// TORRENT_LOG(table) << "inserting node: " << id << " " << addr; + return ret; + } + + // if there is no room, we look for nodes marked as stale + // in the k-bucket. If we find one, we can replace it. + // A node is considered stale if it has failed at least one + // time. Here we choose the node that has failed most times. + // If we don't find one, place this node in the replacement- + // cache and replace any nodes that will fail in the future + // with nodes from that cache. + + i = std::max_element(b.begin(), b.end() + , bind(std::less() + , bind(&node_entry::fail_count, _1) + , bind(&node_entry::fail_count, _2))); + + if (i != b.end() && i->fail_count > 0) + { + // i points to a node that has been marked + // as stale. Replace it with this new one + b.erase(i); + b.push_back(node_entry(id, addr)); +// TORRENT_LOG(table) << "replacing stale node: " << id << " " << addr; + return ret; + } + + // if we don't have any identified stale nodes in + // the bucket, and the bucket is full, we have to + // cache this node and wait until some node fails + // and then replace it. + + bucket_t& rb = m_buckets[bucket_index].second; + + i = std::find_if(rb.begin(), rb.end() + , bind(std::equal_to() + , bind(&node_entry::id, _1), id)); + + // if the node is already in the replacement bucket + // just return. + if (i != rb.end()) return ret; + + if ((int)rb.size() > m_bucket_size) rb.erase(rb.begin()); + rb.push_back(node_entry(id, addr)); +// TORRENT_LOG(table) << "inserting node in replacement cache: " << id << " " << addr; + return ret; +} + +bool routing_table::need_bootstrap() const +{ + for (const_iterator i = begin(); i != end(); ++i) + { + if (i->fail_count == 0) return false; + } + return true; +} + +// fills the vector with the k nodes from our buckets that +// are nearest to the given id. +void routing_table::find_node(node_id const& target + , std::vector& l, bool include_self, int count) +{ + l.clear(); + if (count == 0) count = m_bucket_size; + l.reserve(count); + + int bucket_index = distance_exp(m_id, target); + bucket_t& b = m_buckets[bucket_index].first; + + // copy all nodes that hasn't failed into the target + // vector. + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) + , bind(&node_entry::fail_count, _1)); + assert((int)l.size() <= count); + + if ((int)l.size() == count) + { + assert(std::count_if(l.begin(), l.end() + , boost::bind(std::not_equal_to() + , boost::bind(&node_entry::fail_count, _1), 0)) == 0); + return; + } + + // if we didn't have enough nodes in that bucket + // we have to reply with nodes from buckets closer + // to us. i.e. all the buckets in the range + // [0, bucket_index) if we are to include ourself + // or [1, bucket_index) if not. + bucket_t tmpb; + for (int i = include_self?0:1; i < count; ++i) + { + bucket_t& b = m_buckets[i].first; + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(tmpb) + , bind(&node_entry::fail_count, _1)); + } + + std::random_shuffle(tmpb.begin(), tmpb.end()); + size_t to_copy = (std::min)(m_bucket_size - l.size() + , tmpb.size()); + std::copy(tmpb.begin(), tmpb.begin() + to_copy + , std::back_inserter(l)); + + assert((int)l.size() <= m_bucket_size); + + // return if we have enough nodes or if the bucket index + // is the biggest index available (there are no more buckets) + // to look in. + if ((int)l.size() == count + || bucket_index == (int)m_buckets.size() - 1) + { + assert(std::count_if(l.begin(), l.end() + , boost::bind(std::not_equal_to() + , boost::bind(&node_entry::fail_count, _1), 0)) == 0); + return; + } + + for (size_t i = bucket_index + 1; i < m_buckets.size(); ++i) + { + bucket_t& b = m_buckets[i].first; + + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) + , bind(&node_entry::fail_count, _1)); + if ((int)l.size() >= count) + { + l.erase(l.begin() + count, l.end()); + assert(std::count_if(l.begin(), l.end() + , boost::bind(std::not_equal_to() + , boost::bind(&node_entry::fail_count, _1), 0)) == 0); + return; + } + } + assert((int)l.size() == count + || std::distance(l.begin(), l.end()) < m_bucket_size); + assert((int)l.size() <= count); + + assert(std::count_if(l.begin(), l.end() + , boost::bind(std::not_equal_to() + , boost::bind(&node_entry::fail_count, _1), 0)) == 0); +} + +routing_table::iterator routing_table::begin() const +{ + return iterator(m_buckets.begin(), m_buckets.end()); +} + +routing_table::iterator routing_table::end() const +{ + return iterator(m_buckets.end(), m_buckets.end()); +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/rpc_manager.cpp b/src/kademlia/rpc_manager.cpp new file mode 100644 index 000000000..852c55818 --- /dev/null +++ b/src/kademlia/rpc_manager.cpp @@ -0,0 +1,343 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using boost::posix_time::ptime; +using boost::posix_time::time_duration; +using boost::posix_time::microsec_clock; +using boost::posix_time::seconds; +using boost::posix_time::milliseconds; +using boost::shared_ptr; + +namespace libtorrent { namespace dht +{ + +namespace io = libtorrent::detail; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(rpc) +#endif + +node_id generate_id(); + +rpc_manager::rpc_manager(fun const& f, node_id const& our_id + , routing_table& table, send_fun const& sf) + : m_next_transaction_id(rand() % max_transactions) + , m_oldest_transaction_id(m_next_transaction_id) + , m_incoming(f) + , m_send(sf) + , m_our_id(our_id) + , m_table(table) + , m_timer(boost::posix_time::microsec_clock::universal_time()) + , m_random_number(generate_id()) +{ + std::srand(time(0)); +} + +rpc_manager::~rpc_manager() +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Destructing"; +#endif +} + +#ifndef NDEBUG +void rpc_manager::check_invariant() const +{ + assert(m_oldest_transaction_id >= 0); + assert(m_oldest_transaction_id < max_transactions); + assert(m_next_transaction_id >= 0); + assert(m_next_transaction_id < max_transactions); + + for (int i = m_next_transaction_id; i != m_oldest_transaction_id; + i = (i + 1) % max_transactions) + { + assert(!m_transactions[i]); + } +} +#endif + +bool rpc_manager::incoming(msg const& m) +{ + INVARIANT_CHECK; + + if (m.reply) + { + // if we don't have the transaction id in our + // request list, ignore the packet + + if (m.transaction_id.size() != 2) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with invalid transaction id size: " + << m.transaction_id.size() << " from " << m.addr; +#endif + return false; + } + + std::string::const_iterator i = m.transaction_id.begin(); + int tid = io::read_uint16(i); + + if (tid >= (int)m_transactions.size() + || tid < 0) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with unknown transaction id: " + << tid << " from " << m.addr; +#endif + return false; + } + + boost::shared_ptr o = m_transactions[tid]; + + if (!o) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with unknown transaction id: " + << tid << " from " << m.addr; +#endif + return false; + } + + if (m.addr != o->target_addr) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Reply with incorrect address and valid transaction id: " + << tid << " from " << m.addr; +#endif + return false; + } + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + std::ofstream reply_stats("libtorrent_logs/round_trip_ms.log", std::ios::app); + reply_stats << m.addr << "\t" << (microsec_clock::universal_time() + - o->sent).total_milliseconds() << std::endl; +#endif + o->reply(m); + m_transactions[tid].reset(); + + if (m.piggy_backed_ping) + { + // there is a ping request piggy + // backed in this reply + msg ph; + ph.message_id = messages::ping; + ph.transaction_id = m.ping_transaction_id; + ph.id = m_our_id; + ph.addr = m.addr; + + msg empty; + + reply(empty, ph); + } + return m_table.node_seen(m.id, m.addr); + } + else + { + // this is an incoming request + m_incoming(m); + } + return false; +} + +time_duration rpc_manager::tick() +{ + INVARIANT_CHECK; + + using boost::posix_time::microsec_clock; + + const int timeout_ms = 20 * 1000; + + // look for observers that has timed out + + if (m_next_transaction_id == m_oldest_transaction_id) return milliseconds(timeout_ms); + + for (;m_next_transaction_id != m_oldest_transaction_id; + m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions) + { + assert(m_oldest_transaction_id >= 0); + assert(m_oldest_transaction_id < max_transactions); + + boost::shared_ptr& o = m_transactions[m_oldest_transaction_id]; + if (!o) continue; + + time_duration diff = o->sent + milliseconds(timeout_ms) + - microsec_clock::universal_time(); + if (diff > seconds(0)) + { + if (diff < seconds(1)) return seconds(1); + return diff; + } + + o->timeout(); + o.reset(); + } + return milliseconds(timeout_ms); +} + +unsigned int rpc_manager::new_transaction_id() +{ + INVARIANT_CHECK; + + unsigned int tid = m_next_transaction_id; + m_next_transaction_id = (m_next_transaction_id + 1) % max_transactions; + assert(!m_transactions[tid]); + if (m_transactions[m_next_transaction_id]) + { + m_transactions[m_next_transaction_id]->timeout(); + m_transactions[m_next_transaction_id].reset(); + assert(m_oldest_transaction_id == m_next_transaction_id); + } + if (m_oldest_transaction_id == m_next_transaction_id) + { + m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions; + update_oldest_transaction_id(); + } + +#ifndef NDEBUG + assert(!m_transactions[m_next_transaction_id]); + for (int i = (m_next_transaction_id + 1) % max_transactions; + i != m_oldest_transaction_id; i = (i + 1) % max_transactions) + { + assert(!m_transactions[i]); + } +#endif + + return tid; +} + +void rpc_manager::update_oldest_transaction_id() +{ + INVARIANT_CHECK; + + while (!m_transactions[m_oldest_transaction_id]) + { + m_oldest_transaction_id = (m_oldest_transaction_id + 1) + % max_transactions; + if (m_oldest_transaction_id == m_next_transaction_id) + break; + } +} + +void rpc_manager::invoke(int message_id, udp::endpoint target_addr + , shared_ptr o) +{ + INVARIANT_CHECK; + + msg m; + m.message_id = message_id; + m.reply = false; + m.id = m_our_id; + m.addr = target_addr; + int tid = new_transaction_id(); + m.transaction_id.clear(); + std::back_insert_iterator out(m.transaction_id); + io::write_uint16(tid, out); + + o->send(m); + + m_transactions[tid] = o; + o->sent = boost::posix_time::microsec_clock::universal_time(); + o->target_addr = target_addr; + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "Invoking " << messages::ids[message_id] + << " -> " << target_addr; +#endif + m_send(m); +} + +void rpc_manager::reply(msg& m, msg const& reply_to) +{ + INVARIANT_CHECK; + + if (m.message_id != messages::error) + m.message_id = reply_to.message_id; + m.addr = reply_to.addr; + m.reply = true; + m.piggy_backed_ping = false; + m.id = m_our_id; + m.transaction_id = reply_to.transaction_id; + + m_send(m); +} + +namespace +{ + struct dummy_observer : observer + { + virtual void reply(msg const&) {} + virtual void timeout() {} + virtual void send(msg&) {} + }; +} + +void rpc_manager::reply_with_ping(msg& m, msg const& reply_to) +{ + INVARIANT_CHECK; + + if (m.message_id != messages::error) + m.message_id = reply_to.message_id; + m.addr = reply_to.addr; + m.reply = true; + m.piggy_backed_ping = true; + m.id = m_our_id; + m.transaction_id = reply_to.transaction_id; + + int ptid = new_transaction_id(); + m.ping_transaction_id.clear(); + std::back_insert_iterator out(m.ping_transaction_id); + io::write_uint16(ptid, out); + + boost::shared_ptr o(new dummy_observer); + m_transactions[ptid] = o; + o->sent = boost::posix_time::microsec_clock::universal_time(); + o->target_addr = m.addr; + + m_send(m); +} + + + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/traversal_algorithm.cpp b/src/kademlia/traversal_algorithm.cpp new file mode 100644 index 000000000..6a1e431cf --- /dev/null +++ b/src/kademlia/traversal_algorithm.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2006, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include + +using boost::bind; +using asio::ip::udp; + +namespace libtorrent { namespace dht +{ +#ifdef TORRENT_DHT_VERBOSE_LOGGING +TORRENT_DEFINE_LOG(traversal) +#endif + +void traversal_algorithm::add_entry(node_id const& id, udp::endpoint addr, unsigned char flags) +{ + if (m_failed.find(addr) != m_failed.end()) return; + + result const entry(id, addr, flags); + + std::vector::iterator i = std::lower_bound( + m_results.begin() + , m_results.end() + , entry + , bind( + compare_ref + , bind(&result::id, _1) + , bind(&result::id, _2) + , m_target + ) + ); + + if (i == m_results.end() || i->id != id) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "adding result: " << id << " " << addr; +#endif + m_results.insert(i, entry); + } +} + +void traversal_algorithm::traverse(node_id const& id, udp::endpoint addr) +{ + add_entry(id, addr, 0); +} + +void traversal_algorithm::finished(node_id const& id) +{ + m_invoke_count--; + add_requests(); + if (m_invoke_count == 0) done(); +} + +void traversal_algorithm::failed(node_id const& id) +{ + m_invoke_count--; + + std::vector::iterator i = std::find_if( + m_results.begin() + , m_results.end() + , bind( + std::equal_to() + , bind(&result::id, _1) + , id + ) + ); + + assert(i != m_results.end()); + + assert(i->flags & result::queried); + m_failed.insert(i->addr); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "failed: " << i->id << " " << i->addr; +#endif + m_results.erase(i); + m_table.node_failed(id); + add_requests(); + if (m_invoke_count == 0) done(); +} + +void traversal_algorithm::add_request(node_id const& id, udp::endpoint addr) +{ + invoke(id, addr); + m_invoke_count++; +} + +namespace +{ + bool bitwise_nand(unsigned char lhs, unsigned char rhs) + { + return (lhs & rhs) == 0; + } +} + +void traversal_algorithm::add_requests() +{ + while (m_invoke_count < m_branch_factor) + { + // Find the first node that hasn't already been queried. + // TODO: Better heuristic + std::vector::iterator i = std::find_if( + m_results.begin() + , last_iterator() + , bind( + &bitwise_nand + , bind(&result::flags, _1) + , (unsigned char)result::queried + ) + ); +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(traversal) << "nodes left (" << this << "): " << (last_iterator() - i); +#endif + + if (i == last_iterator()) break; + + add_request(i->id, i->addr); + i->flags |= result::queried; + } +} + +std::vector::iterator traversal_algorithm::last_iterator() +{ + return (int)m_results.size() >= m_max_results ? + m_results.begin() + m_max_results + : m_results.end(); +} + +} } // namespace libtorrent::dht + diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 696da1171..3a5b8cf3f 100755 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -1081,6 +1081,10 @@ namespace libtorrent using namespace boost::posix_time; (*m_logger) << to_simple_string(second_clock::universal_time()) << " <== DHT_PORT [ p: " << listen_port << " ]\n"; +#endif +#ifndef TORRENT_DISABLE_DHT + if (m_ses.m_dht) m_ses.m_dht->add_node(udp::endpoint( + m_remote.address(), listen_port)); #endif } diff --git a/src/session.cpp b/src/session.cpp index fdcb75bd1..ee53dbce7 100755 --- a/src/session.cpp +++ b/src/session.cpp @@ -70,6 +70,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/bt_peer_connection.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/socket.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" using namespace boost::posix_time; using boost::shared_ptr; @@ -430,7 +431,7 @@ namespace libtorrent { namespace detail session_impl::session_impl( std::pair listen_port_range - , const fingerprint& cl_fprint + , fingerprint const& cl_fprint , char const* listen_interface) : m_tracker_manager(m_settings) , m_listen_port_range(listen_port_range) @@ -858,14 +859,11 @@ namespace libtorrent { namespace detail } } - // tick() will set the used upload quota + // second_tick() will set the used upload quota t.second_tick(m_stat, tick_interval); ++i; } - // don't pass in the tick_interval here, because - // the stats have already been adjusted in - // the peer's second tick. m_stat.second_tick(tick_interval); // distribute the maximum upload rate among the torrents @@ -959,10 +957,10 @@ namespace libtorrent { namespace detail } catch (std::exception& e) { - #ifndef NDEBUG +#ifndef NDEBUG std::cerr << e.what() << "\n"; std::string err = e.what(); - #endif +#endif assert(false); } @@ -984,13 +982,20 @@ namespace libtorrent { namespace detail m_tracker_manager.queue_request(m_selector, req, login); } } - tracker_timer.expires_from_now(boost::posix_time::seconds( - m_settings.stop_tracker_timeout)); - tracker_timer.async_wait(bind(&demuxer::interrupt, &m_selector)); + + ptime start(microsec_clock::universal_time()); l.unlock(); - m_selector.reset(); - m_selector.run(); + while (microsec_clock::universal_time() - start < seconds( + m_settings.stop_tracker_timeout) + && !m_tracker_manager.empty()) + { + tracker_timer.expires_from_now(boost::posix_time::milliseconds(100)); + tracker_timer.async_wait(bind(&demuxer::interrupt, &m_selector)); + + m_selector.reset(); + m_selector.run(); + } l.lock(); assert(m_abort); @@ -1263,6 +1268,14 @@ namespace libtorrent d->info_hash = ti.info_hash(); d->resume_data = resume_data; +#ifndef TORRENT_DISABLE_DHT + torrent_info::nodes_t const& nodes = ti.nodes(); + std::for_each(nodes.begin(), nodes.end(), bind( + (void(dht::dht_tracker::*)(std::pair const&)) + &dht::dht_tracker::add_node + , boost::ref(m_impl.m_dht), _1)); +#endif + // add the torrent to the queue to be checked m_checker_impl.m_torrents.push_back(d); // and notify the thread that it got another @@ -1444,6 +1457,43 @@ namespace libtorrent return s; } +#ifndef TORRENT_DISABLE_DHT + void session::start_dht(entry const& startup_state) + { + m_impl.m_dht.reset(new dht::dht_tracker(m_impl.m_selector + , m_impl.m_dht_settings, startup_state)); + } + + void session::stop_dht() + { + m_impl.m_dht.reset(); + } + + void session::set_dht_settings(dht_settings const& settings) + { + if (settings.service_port != m_impl.m_dht_settings.service_port + && m_impl.m_dht) + { + assert(false); // not implemented yet + // TODO: change dht service port! + } + m_impl.m_dht_settings = settings; + } + + entry session::dht_state() const + { + assert(m_impl.m_dht); + return m_impl.m_dht->state(); + } + + void session::add_dht_node(std::pair const& node) + { + assert(m_impl.m_dht); + m_impl.m_dht->add_node(node); + } + +#endif + bool session::is_listening() const { session_impl::mutex_t::scoped_lock l(m_impl.m_mutex); diff --git a/src/torrent.cpp b/src/torrent.cpp index 765e09e6e..89dcda7c2 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -211,6 +211,9 @@ namespace libtorrent , m_complete(-1) , m_incomplete(-1) , m_host_resolver(ses.m_selector) +#ifndef TORRENT_DISABLE_DHT + , m_dht_announce_timer(ses.m_selector) +#endif , m_policy() , m_ses(ses) , m_checker(checker) @@ -276,6 +279,14 @@ namespace libtorrent m_policy.reset(new policy(this)); init(); + +#ifndef TORRENT_DISABLE_DHT + if (!tf.priv()) + { + m_dht_announce_timer.expires_from_now(seconds(10)); + m_dht_announce_timer.async_wait(bind(&torrent::on_dht_announce, this, _1)); + } +#endif } torrent::torrent( @@ -300,6 +311,9 @@ namespace libtorrent , m_complete(-1) , m_incomplete(-1) , m_host_resolver(ses.m_selector) +#ifndef TORRENT_DISABLE_DHT + , m_dht_announce_timer(ses.m_selector) +#endif , m_policy() , m_ses(ses) , m_checker(checker) @@ -368,6 +382,10 @@ namespace libtorrent m_policy.reset(new policy(this)); m_torrent_file.add_tracker(tracker_url); +#ifndef TORRENT_DISABLE_DHT + m_dht_announce_timer.expires_from_now(seconds(10)); + m_dht_announce_timer.async_wait(bind(&torrent::on_dht_announce, this, _1)); +#endif } torrent::~torrent() @@ -419,11 +437,34 @@ namespace libtorrent m_net_interface = tcp::endpoint(address::from_string(net_interface), 0); } +#ifndef TORRENT_DISABLE_DHT + + void torrent::on_dht_announce(asio::error const& e) + { + if (e) return; + m_dht_announce_timer.expires_from_now(boost::posix_time::minutes(30)); + m_dht_announce_timer.async_wait(bind(&torrent::on_dht_announce, this, _1)); + if (!m_ses.m_dht) return; + m_ses.m_dht->announce(m_torrent_file.info_hash() + , m_ses.m_listen_interface.port() + , bind(&torrent::on_dht_announce_response, this, _1)); + } + + void torrent::on_dht_announce_response(std::vector const& peers) + { + std::for_each(peers.begin(), peers.end(), bind( + &policy::peer_from_tracker, boost::ref(m_policy), _1, peer_id(0))); + } + +#endif + // returns true if it is time for this torrent to make another // tracker request bool torrent::should_request() { INVARIANT_CHECK; + + if (m_torrent_file.trackers().empty()) return false; if (m_just_paused) { @@ -962,22 +1003,23 @@ namespace libtorrent m_resolving_web_seeds.insert(url); if (m_ses.m_settings.proxy_ip.empty()) { - tcp::resolver::query q(hostname, "0"); + tcp::resolver::query q(hostname, boost::lexical_cast(port)); m_host_resolver.async_resolve(q, bind(&torrent::on_name_lookup - , shared_from_this(), _1, _2, port, url)); + , shared_from_this(), _1, _2, url)); } else { // use proxy - tcp::resolver::query q(m_ses.m_settings.proxy_ip, "0"); + tcp::resolver::query q(m_ses.m_settings.proxy_ip + , boost::lexical_cast(m_ses.m_settings.proxy_port)); m_host_resolver.async_resolve(q, bind(&torrent::on_name_lookup - , shared_from_this(), _1, _2, m_ses.m_settings.proxy_port, url)); + , shared_from_this(), _1, _2, url)); } } void torrent::on_name_lookup(asio::error const& e, tcp::resolver::iterator host - , int port, std::string url) try + , std::string url) try { detail::session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -1012,7 +1054,7 @@ namespace libtorrent if (m_ses.m_abort) return; - tcp::endpoint a(host->endpoint().address(), port); + tcp::endpoint a(host->endpoint()); boost::shared_ptr s(new stream_socket(m_ses.m_selector)); boost::intrusive_ptr c(new web_peer_connection( diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index af7f37cd9..2e1aafe95 100755 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -127,6 +127,7 @@ namespace libtorrent torrent_info::torrent_info(const entry& torrent_file) : m_creation_date(date(not_a_date_time)) , m_multifile(false) + , m_private(false) , m_extra_info(entry::dictionary_t) { try @@ -269,6 +270,13 @@ namespace libtorrent m_extra_info[i->first] = i->second; } + if (info.find_key("private")) + { + // this key exists, don't care about its value, consider + // the torrent private + m_private = true; + } + #ifndef NDEBUG std::vector info_section_buf; entry gen_info_section = create_info_metadata(); @@ -319,10 +327,27 @@ namespace libtorrent } std::random_shuffle(start, stop); } - else + else if (entry const* i = torrent_file.find_key("announce")) { - m_urls.push_back(announce_entry( - torrent_file["announce"].string())); + m_urls.push_back(announce_entry(i->string())); + } + + if (entry const* i = torrent_file.find_key("nodes")) + { + entry::list_type const& list = i->list(); + for (entry::list_type::const_iterator i(list.begin()) + , end(list.end()); i != end; ++i) + { + if (i->type() != entry::list_t) continue; + entry::list_type const& l = i->list(); + entry::list_type::const_iterator iter = l.begin(); + if (l.size() < 1) continue; + std::string const& hostname = iter->string(); + ++iter; + int port = 6881; + if (list.end() != iter) port = iter->integer(); + m_nodes.push_back(std::make_pair(hostname, port)); + } } // extract creation date @@ -518,14 +543,32 @@ namespace libtorrent entry dict(entry::dictionary_t); - if (m_urls.empty() || m_files.empty()) + if ((m_urls.empty() && m_nodes.empty()) || m_files.empty()) { // TODO: throw something here // throw return entry(); } - dict["announce"] = m_urls.front().url; + if (m_private) dict["private"] = 1; + + if (!m_urls.empty()) + dict["announce"] = m_urls.front().url; + + if (!m_nodes.empty()) + { + entry& nodes = dict["nodes"]; + nodes = entry(entry::list_t); + entry::list_type& nodes_list = nodes.list(); + for (nodes_t::const_iterator i = m_nodes.begin() + , end(m_nodes.end()); i != end; ++i) + { + entry::list_type node; + node.push_back(entry(i->first)); + node.push_back(entry(i->second)); + nodes_list.push_back(entry(node)); + } + } if (m_urls.size() > 1) { @@ -630,7 +673,12 @@ namespace libtorrent else return piece_length(); } - + + void torrent_info::add_node(std::pair const& node) + { + m_nodes.push_back(node); + } + std::vector torrent_info::map_block(int piece, size_type offset , int size) const { diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp index 331c3a8ea..20e234c1f 100755 --- a/src/tracker_manager.cpp +++ b/src/tracker_manager.cpp @@ -559,4 +559,11 @@ namespace libtorrent std::swap(m_connections, keep_connections); } + + bool tracker_manager::empty() const + { + mutex_t::scoped_lock l(m_mutex); + return m_connections.empty(); + } + } diff --git a/src/web_peer_connection.cpp b/src/web_peer_connection.cpp index 9ca5a5846..5f262059c 100755 --- a/src/web_peer_connection.cpp +++ b/src/web_peer_connection.cpp @@ -66,6 +66,8 @@ namespace libtorrent { INVARIANT_CHECK; + m_max_out_request_queue = ses.m_settings.urlseed_pipeline_size; + // since this is a web seed, change the timeout // according to the settings. set_timeout(ses.m_settings.urlseed_timeout);