twisterp2pblockchainnetworkbittorrentmicrobloggingipv6social-networkdhtdecentralizedp2p-networktwister-servertwister-ipv6twister-coretwisterarmy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6574 lines
197 KiB
6574 lines
197 KiB
/* |
|
|
|
Copyright (c) 2006-2012, Arvid Norberg, Magnus Jonsson |
|
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 "libtorrent/pch.hpp" |
|
|
|
#include "../../src/util.h" |
|
|
|
#include <ctime> |
|
#include <algorithm> |
|
#include <set> |
|
#include <cctype> |
|
#include <algorithm> |
|
|
|
#ifdef _MSC_VER |
|
#pragma warning(push, 1) |
|
#endif |
|
|
|
#include <boost/limits.hpp> |
|
#include <boost/bind.hpp> |
|
#include <boost/function_equal.hpp> |
|
|
|
#ifdef _MSC_VER |
|
#pragma warning(pop) |
|
#endif |
|
|
|
#include "libtorrent/peer_id.hpp" |
|
#include "libtorrent/torrent_info.hpp" |
|
#include "libtorrent/tracker_manager.hpp" |
|
#include "libtorrent/bencode.hpp" |
|
#include "libtorrent/hasher.hpp" |
|
#include "libtorrent/entry.hpp" |
|
#include "libtorrent/session.hpp" |
|
#include "libtorrent/fingerprint.hpp" |
|
#include "libtorrent/entry.hpp" |
|
#include "libtorrent/alert_types.hpp" |
|
#include "libtorrent/invariant_check.hpp" |
|
#include "libtorrent/file.hpp" |
|
#include "libtorrent/bt_peer_connection.hpp" |
|
#include "libtorrent/ip_filter.hpp" |
|
#include "libtorrent/socket.hpp" |
|
#include "libtorrent/aux_/session_impl.hpp" |
|
#ifndef TORRENT_DISABLE_DHT |
|
#include "libtorrent/kademlia/dht_tracker.hpp" |
|
#endif |
|
#include "libtorrent/enum_net.hpp" |
|
#include "libtorrent/config.hpp" |
|
#include "libtorrent/utf8.hpp" |
|
#include "libtorrent/upnp.hpp" |
|
#include "libtorrent/natpmp.hpp" |
|
#include "libtorrent/lsd.hpp" |
|
#include "libtorrent/instantiate_connection.hpp" |
|
#include "libtorrent/peer_info.hpp" |
|
#include "libtorrent/settings.hpp" |
|
#include "libtorrent/build_config.hpp" |
|
#include "libtorrent/extensions.hpp" |
|
#include "libtorrent/random.hpp" |
|
#include "libtorrent/magnet_uri.hpp" |
|
|
|
#include "twister.h" // for LIBTORRENT_PORT_OFFSET |
|
|
|
#if defined TORRENT_STATS && defined __MACH__ |
|
#include <mach/task.h> |
|
#endif |
|
|
|
#ifndef TORRENT_WINDOWS |
|
#include <sys/resource.h> |
|
#endif |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
|
|
// for logging stat layout |
|
#include "libtorrent/stat.hpp" |
|
#include "libtorrent/struct_debug.hpp" |
|
|
|
// for logging the size of DHT structures |
|
#ifndef TORRENT_DISABLE_DHT |
|
#include <libtorrent/kademlia/find_data.hpp> |
|
#include <libtorrent/kademlia/refresh.hpp> |
|
#include <libtorrent/kademlia/node.hpp> |
|
#include <libtorrent/kademlia/observer.hpp> |
|
#endif // TORRENT_DISABLE_DHT |
|
|
|
#include "libtorrent/http_tracker_connection.hpp" |
|
#include "libtorrent/udp_tracker_connection.hpp" |
|
|
|
#include "libtorrent/debug.hpp" |
|
|
|
#if TORRENT_USE_IOSTREAM |
|
namespace libtorrent { |
|
std::ofstream logger::log_file; |
|
std::string logger::open_filename; |
|
mutex logger::file_mutex; |
|
} |
|
#endif // TORRENT_USE_IOSTREAM |
|
|
|
#endif |
|
|
|
#ifdef TORRENT_USE_GCRYPT |
|
|
|
extern "C" { |
|
GCRY_THREAD_OPTION_PTHREAD_IMPL; |
|
} |
|
|
|
namespace |
|
{ |
|
// libgcrypt requires this to initialize the library |
|
struct gcrypt_setup |
|
{ |
|
gcrypt_setup() |
|
{ |
|
gcry_check_version(0); |
|
gcry_error_t e = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); |
|
if (e != 0) fprintf(stderr, "libcrypt ERROR: %s\n", gcry_strerror(e)); |
|
e = gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); |
|
if (e != 0) fprintf(stderr, "initialization finished error: %s\n", gcry_strerror(e)); |
|
} |
|
} gcrypt_global_constructor; |
|
} |
|
|
|
#endif // TORRENT_USE_GCRYPT |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
|
|
#include <openssl/crypto.h> |
|
|
|
namespace |
|
{ |
|
// openssl requires this to clean up internal |
|
// structures it allocates |
|
struct openssl_cleanup |
|
{ |
|
~openssl_cleanup() { CRYPTO_cleanup_all_ex_data(); } |
|
} openssl_global_destructor; |
|
} |
|
|
|
#endif // TORRENT_USE_OPENSSL |
|
|
|
#ifdef TORRENT_WINDOWS |
|
// for ERROR_SEM_TIMEOUT |
|
#include <winerror.h> |
|
#endif |
|
|
|
using boost::shared_ptr; |
|
using boost::weak_ptr; |
|
using libtorrent::aux::session_impl; |
|
|
|
#ifdef BOOST_NO_EXCEPTIONS |
|
namespace boost { |
|
void throw_exception(std::exception const& e) { ::abort(); } |
|
} |
|
#endif |
|
|
|
namespace libtorrent { |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
std::map<std::string, async_t> _async_ops; |
|
int _async_ops_nthreads = 0; |
|
mutex _async_ops_mutex; |
|
#endif |
|
|
|
namespace detail |
|
{ |
|
|
|
std::string generate_auth_string(std::string const& user |
|
, std::string const& passwd) |
|
{ |
|
if (user.empty()) return std::string(); |
|
return user + ":" + passwd; |
|
} |
|
} |
|
|
|
namespace aux { |
|
|
|
#ifdef TORRENT_STATS |
|
void get_vm_stats(vm_statistics_data_t* vm_stat) |
|
{ |
|
memset(vm_stat, 0, sizeof(*vm_stat)); |
|
#if defined __MACH__ |
|
mach_port_t host_port = mach_host_self(); |
|
mach_msg_type_number_t host_count = HOST_VM_INFO_COUNT; |
|
kern_return_t error = host_statistics(host_port, HOST_VM_INFO, |
|
(host_info_t)vm_stat, &host_count); |
|
#elif defined TORRENT_LINUX |
|
char buffer[4096]; |
|
char string[1024]; |
|
boost::uint32_t value; |
|
FILE* f = fopen("/proc/vmstat", "r"); |
|
int ret = 0; |
|
while ((ret = fscanf(f, "%s %u\n", string, &value)) != EOF) |
|
{ |
|
if (ret != 2) continue; |
|
if (strcmp(string, "nr_active_anon") == 0) vm_stat->active_count += value; |
|
else if (strcmp(string, "nr_active_file") == 0) vm_stat->active_count += value; |
|
else if (strcmp(string, "nr_inactive_anon") == 0) vm_stat->inactive_count += value; |
|
else if (strcmp(string, "nr_inactive_file") == 0) vm_stat->inactive_count += value; |
|
else if (strcmp(string, "nr_free_pages") == 0) vm_stat->free_count = value; |
|
else if (strcmp(string, "nr_unevictable") == 0) vm_stat->wire_count = value; |
|
else if (strcmp(string, "pswpin") == 0) vm_stat->pageins = value; |
|
else if (strcmp(string, "pswpout") == 0) vm_stat->pageouts = value; |
|
else if (strcmp(string, "pgfault") == 0) vm_stat->faults = value; |
|
} |
|
fclose(f); |
|
#endif |
|
// TOOD: windows? |
|
} |
|
|
|
void get_thread_cpu_usage(thread_cpu_usage* tu) |
|
{ |
|
#if defined __MACH__ |
|
task_thread_times_info t_info; |
|
mach_msg_type_number_t t_info_count = TASK_THREAD_TIMES_INFO_COUNT; |
|
task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, (task_info_t)&t_info, &t_info_count); |
|
|
|
tu->user_time = min_time() |
|
+ seconds(t_info.user_time.seconds) |
|
+ microsec(t_info.user_time.microseconds); |
|
tu->system_time = min_time() |
|
+ seconds(t_info.system_time.seconds) |
|
+ microsec(t_info.system_time.microseconds); |
|
#elif defined TORRENT_LINUX |
|
struct rusage ru; |
|
getrusage(RUSAGE_THREAD, &ru); |
|
tu->user_time = min_time() |
|
+ seconds(ru.ru_utime.tv_sec) |
|
+ microsec(ru.ru_utime.tv_usec); |
|
tu->system_time = min_time() |
|
+ seconds(ru.ru_stime.tv_sec) |
|
+ microsec(ru.ru_stime.tv_usec); |
|
#elif defined TORRENT_WINDOWS |
|
FILETIME system_time; |
|
FILETIME user_time; |
|
FILETIME creation_time; |
|
FILETIME exit_time; |
|
GetThreadTimes(GetCurrentThread(), &creation_time, &exit_time, &user_time, &system_time); |
|
|
|
boost::uint64_t utime = (boost::uint64_t(user_time.dwHighDateTime) << 32) |
|
+ user_time.dwLowDateTime; |
|
boost::uint64_t stime = (boost::uint64_t(system_time.dwHighDateTime) << 32) |
|
+ system_time.dwLowDateTime; |
|
|
|
tu->user_time = min_time() + microsec(utime / 10); |
|
tu->system_time = min_time() + microsec(stime / 10); |
|
#endif |
|
} |
|
#endif //TORRENT_STATS |
|
|
|
struct seed_random_generator |
|
{ |
|
seed_random_generator() |
|
{ |
|
random_seed((unsigned int)total_microseconds(time_now_hires() - min_time())); |
|
} |
|
}; |
|
|
|
#define TORRENT_SETTING(t, x) {#x, offsetof(session_settings,x), t}, |
|
|
|
bencode_map_entry session_settings_map[] = |
|
{ |
|
TORRENT_SETTING(std_string, user_agent) |
|
TORRENT_SETTING(integer, tracker_completion_timeout) |
|
TORRENT_SETTING(integer, tracker_receive_timeout) |
|
TORRENT_SETTING(integer, stop_tracker_timeout) |
|
TORRENT_SETTING(integer, tracker_maximum_response_length) |
|
TORRENT_SETTING(integer, piece_timeout) |
|
TORRENT_SETTING(integer, request_timeout) |
|
TORRENT_SETTING(integer, request_queue_time) |
|
TORRENT_SETTING(integer, max_allowed_in_request_queue) |
|
TORRENT_SETTING(integer, max_out_request_queue) |
|
TORRENT_SETTING(integer, whole_pieces_threshold) |
|
TORRENT_SETTING(integer, peer_timeout) |
|
TORRENT_SETTING(integer, urlseed_timeout) |
|
TORRENT_SETTING(integer, urlseed_pipeline_size) |
|
TORRENT_SETTING(integer, urlseed_wait_retry) |
|
TORRENT_SETTING(integer, file_pool_size) |
|
TORRENT_SETTING(boolean, allow_multiple_connections_per_ip) |
|
TORRENT_SETTING(integer, max_failcount) |
|
TORRENT_SETTING(integer, min_reconnect_time) |
|
TORRENT_SETTING(integer, peer_connect_timeout) |
|
TORRENT_SETTING(boolean, ignore_limits_on_local_network) |
|
TORRENT_SETTING(integer, connection_speed) |
|
TORRENT_SETTING(boolean, send_redundant_have) |
|
TORRENT_SETTING(boolean, lazy_bitfields) |
|
TORRENT_SETTING(integer, inactivity_timeout) |
|
TORRENT_SETTING(integer, unchoke_interval) |
|
TORRENT_SETTING(integer, optimistic_unchoke_interval) |
|
TORRENT_SETTING(std_string, announce_ip) |
|
TORRENT_SETTING(integer, num_want) |
|
TORRENT_SETTING(integer, initial_picker_threshold) |
|
TORRENT_SETTING(integer, allowed_fast_set_size) |
|
TORRENT_SETTING(integer, suggest_mode) |
|
TORRENT_SETTING(integer, max_queued_disk_bytes) |
|
TORRENT_SETTING(integer, max_queued_disk_bytes_low_watermark) |
|
TORRENT_SETTING(integer, handshake_timeout) |
|
#ifndef TORRENT_DISABLE_DHT |
|
TORRENT_SETTING(boolean, use_dht_as_fallback) |
|
#endif |
|
TORRENT_SETTING(boolean, free_torrent_hashes) |
|
TORRENT_SETTING(boolean, upnp_ignore_nonrouters) |
|
TORRENT_SETTING(integer, send_buffer_low_watermark) |
|
TORRENT_SETTING(integer, send_buffer_watermark) |
|
#ifndef TORRENT_NO_DEPRECATE |
|
TORRENT_SETTING(boolean, auto_upload_slots) |
|
TORRENT_SETTING(boolean, auto_upload_slots_rate_based) |
|
#endif |
|
TORRENT_SETTING(integer, choking_algorithm) |
|
TORRENT_SETTING(integer, seed_choking_algorithm) |
|
TORRENT_SETTING(boolean, use_parole_mode) |
|
TORRENT_SETTING(integer, cache_size) |
|
TORRENT_SETTING(integer, cache_buffer_chunk_size) |
|
TORRENT_SETTING(integer, cache_expiry) |
|
TORRENT_SETTING(boolean, use_read_cache) |
|
TORRENT_SETTING(boolean, explicit_read_cache) |
|
TORRENT_SETTING(integer, disk_io_write_mode) |
|
TORRENT_SETTING(integer, disk_io_read_mode) |
|
TORRENT_SETTING(boolean, coalesce_reads) |
|
TORRENT_SETTING(boolean, coalesce_writes) |
|
TORRENT_SETTING(character, peer_tos) |
|
TORRENT_SETTING(integer, active_downloads) |
|
TORRENT_SETTING(integer, active_seeds) |
|
TORRENT_SETTING(integer, active_dht_limit) |
|
TORRENT_SETTING(integer, active_tracker_limit) |
|
TORRENT_SETTING(integer, active_lsd_limit) |
|
TORRENT_SETTING(integer, active_limit) |
|
TORRENT_SETTING(boolean, auto_manage_prefer_seeds) |
|
TORRENT_SETTING(boolean, dont_count_slow_torrents) |
|
TORRENT_SETTING(integer, auto_manage_interval) |
|
TORRENT_SETTING(floating_point, share_ratio_limit) |
|
TORRENT_SETTING(floating_point, seed_time_ratio_limit) |
|
TORRENT_SETTING(integer, seed_time_limit) |
|
TORRENT_SETTING(floating_point, peer_turnover) |
|
TORRENT_SETTING(floating_point, peer_turnover_cutoff) |
|
TORRENT_SETTING(boolean, close_redundant_connections) |
|
TORRENT_SETTING(integer, auto_scrape_interval) |
|
TORRENT_SETTING(integer, auto_scrape_min_interval) |
|
TORRENT_SETTING(integer, max_peerlist_size) |
|
TORRENT_SETTING(integer, max_paused_peerlist_size) |
|
TORRENT_SETTING(integer, min_announce_interval) |
|
TORRENT_SETTING(boolean, prioritize_partial_pieces) |
|
TORRENT_SETTING(integer, auto_manage_startup) |
|
TORRENT_SETTING(boolean, rate_limit_ip_overhead) |
|
TORRENT_SETTING(boolean, announce_to_all_trackers) |
|
TORRENT_SETTING(boolean, announce_to_all_tiers) |
|
TORRENT_SETTING(boolean, prefer_udp_trackers) |
|
TORRENT_SETTING(boolean, strict_super_seeding) |
|
TORRENT_SETTING(integer, seeding_piece_quota) |
|
TORRENT_SETTING(integer, max_sparse_regions) |
|
#ifndef TORRENT_DISABLE_MLOCK |
|
TORRENT_SETTING(boolean, lock_disk_cache) |
|
#endif |
|
TORRENT_SETTING(integer, max_rejects) |
|
TORRENT_SETTING(integer, recv_socket_buffer_size) |
|
TORRENT_SETTING(integer, send_socket_buffer_size) |
|
TORRENT_SETTING(boolean, optimize_hashing_for_speed) |
|
TORRENT_SETTING(integer, file_checks_delay_per_block) |
|
TORRENT_SETTING(integer, disk_cache_algorithm) |
|
TORRENT_SETTING(integer, read_cache_line_size) |
|
TORRENT_SETTING(integer, write_cache_line_size) |
|
TORRENT_SETTING(integer, optimistic_disk_retry) |
|
TORRENT_SETTING(boolean, disable_hash_checks) |
|
TORRENT_SETTING(boolean, allow_reordered_disk_operations) |
|
TORRENT_SETTING(boolean, allow_i2p_mixed) |
|
TORRENT_SETTING(integer, max_suggest_pieces) |
|
TORRENT_SETTING(boolean, drop_skipped_requests) |
|
TORRENT_SETTING(boolean, low_prio_disk) |
|
TORRENT_SETTING(integer, local_service_announce_interval) |
|
TORRENT_SETTING(integer, dht_announce_interval) |
|
TORRENT_SETTING(integer, udp_tracker_token_expiry) |
|
TORRENT_SETTING(boolean, volatile_read_cache) |
|
TORRENT_SETTING(boolean, guided_read_cache) |
|
TORRENT_SETTING(integer, default_cache_min_age) |
|
TORRENT_SETTING(integer, num_optimistic_unchoke_slots) |
|
TORRENT_SETTING(boolean, no_atime_storage) |
|
TORRENT_SETTING(integer, default_est_reciprocation_rate) |
|
TORRENT_SETTING(integer, increase_est_reciprocation_rate) |
|
TORRENT_SETTING(integer, decrease_est_reciprocation_rate) |
|
TORRENT_SETTING(boolean, incoming_starts_queued_torrents) |
|
TORRENT_SETTING(boolean, report_true_downloaded) |
|
TORRENT_SETTING(boolean, strict_end_game_mode) |
|
TORRENT_SETTING(boolean, broadcast_lsd) |
|
TORRENT_SETTING(boolean, enable_outgoing_utp) |
|
TORRENT_SETTING(boolean, enable_incoming_utp) |
|
TORRENT_SETTING(boolean, enable_outgoing_tcp) |
|
TORRENT_SETTING(boolean, enable_incoming_tcp) |
|
TORRENT_SETTING(integer, max_pex_peers) |
|
TORRENT_SETTING(boolean, ignore_resume_timestamps) |
|
TORRENT_SETTING(boolean, no_recheck_incomplete_resume) |
|
TORRENT_SETTING(boolean, anonymous_mode) |
|
TORRENT_SETTING(boolean, force_proxy) |
|
TORRENT_SETTING(integer, tick_interval) |
|
TORRENT_SETTING(boolean, report_web_seed_downloads) |
|
TORRENT_SETTING(integer, share_mode_target) |
|
TORRENT_SETTING(integer, upload_rate_limit) |
|
TORRENT_SETTING(integer, download_rate_limit) |
|
TORRENT_SETTING(integer, local_upload_rate_limit) |
|
TORRENT_SETTING(integer, local_download_rate_limit) |
|
TORRENT_SETTING(integer, dht_upload_rate_limit) |
|
TORRENT_SETTING(integer, unchoke_slots_limit) |
|
TORRENT_SETTING(integer, half_open_limit) |
|
TORRENT_SETTING(integer, connections_limit) |
|
TORRENT_SETTING(integer, utp_target_delay) |
|
TORRENT_SETTING(integer, utp_gain_factor) |
|
TORRENT_SETTING(integer, utp_syn_resends) |
|
TORRENT_SETTING(integer, utp_fin_resends) |
|
TORRENT_SETTING(integer, utp_num_resends) |
|
TORRENT_SETTING(integer, utp_connect_timeout) |
|
#ifndef TORRENT_NO_DEPRECATE |
|
TORRENT_SETTING(integer, utp_delayed_ack) |
|
#endif |
|
TORRENT_SETTING(boolean, utp_dynamic_sock_buf) |
|
TORRENT_SETTING(integer, mixed_mode_algorithm) |
|
TORRENT_SETTING(boolean, rate_limit_utp) |
|
TORRENT_SETTING(integer, listen_queue_size) |
|
TORRENT_SETTING(boolean, announce_double_nat) |
|
TORRENT_SETTING(integer, torrent_connect_boost) |
|
TORRENT_SETTING(boolean, seeding_outgoing_connections) |
|
TORRENT_SETTING(boolean, no_connect_privileged_ports) |
|
TORRENT_SETTING(integer, alert_queue_size) |
|
TORRENT_SETTING(integer, max_metadata_size) |
|
TORRENT_SETTING(boolean, smooth_connects) |
|
TORRENT_SETTING(boolean, always_send_user_agent) |
|
TORRENT_SETTING(boolean, apply_ip_filter_to_trackers) |
|
TORRENT_SETTING(integer, read_job_every) |
|
TORRENT_SETTING(boolean, use_disk_read_ahead) |
|
TORRENT_SETTING(boolean, lock_files) |
|
TORRENT_SETTING(integer, ssl_listen) |
|
TORRENT_SETTING(integer, tracker_backoff) |
|
TORRENT_SETTING(boolean, ban_web_seeds) |
|
TORRENT_SETTING(integer, max_http_recv_buffer_size) |
|
}; |
|
|
|
#undef TORRENT_SETTING |
|
#define TORRENT_SETTING(t, x) {#x, offsetof(proxy_settings,x), t}, |
|
|
|
bencode_map_entry proxy_settings_map[] = |
|
{ |
|
TORRENT_SETTING(std_string, hostname) |
|
TORRENT_SETTING(integer, port) |
|
TORRENT_SETTING(std_string, username) |
|
TORRENT_SETTING(std_string, password) |
|
TORRENT_SETTING(integer, type) |
|
TORRENT_SETTING(boolean, proxy_hostnames) |
|
TORRENT_SETTING(boolean, proxy_peer_connections) |
|
}; |
|
#undef TORRENT_SETTING |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
#define TORRENT_SETTING(t, x) {#x, offsetof(dht_settings,x), t}, |
|
bencode_map_entry dht_settings_map[] = |
|
{ |
|
TORRENT_SETTING(integer, max_peers_reply) |
|
TORRENT_SETTING(integer, search_branching) |
|
#ifndef TORRENT_NO_DEPRECATE |
|
TORRENT_SETTING(integer, service_port) |
|
#endif |
|
TORRENT_SETTING(integer, max_fail_count) |
|
TORRENT_SETTING(integer, max_torrents) |
|
TORRENT_SETTING(integer, max_dht_items) |
|
TORRENT_SETTING(integer, max_torrent_search_reply) |
|
TORRENT_SETTING(boolean, restrict_routing_ips) |
|
TORRENT_SETTING(boolean, restrict_search_ips) |
|
TORRENT_SETTING(boolean, extended_routing_table) |
|
}; |
|
#undef TORRENT_SETTING |
|
#endif |
|
|
|
#ifndef TORRENT_DISABLE_ENCRYPTION |
|
#define TORRENT_SETTING(t, x) {#x, offsetof(pe_settings,x), t}, |
|
bencode_map_entry pe_settings_map[] = |
|
{ |
|
TORRENT_SETTING(integer, out_enc_policy) |
|
TORRENT_SETTING(integer, in_enc_policy) |
|
TORRENT_SETTING(integer, allowed_enc_level) |
|
TORRENT_SETTING(boolean, prefer_rc4) |
|
}; |
|
#undef TORRENT_SETTING |
|
#endif |
|
|
|
struct session_category |
|
{ |
|
char const* name; |
|
bencode_map_entry const* map; |
|
int num_entries; |
|
int flag; |
|
int offset; |
|
int default_offset; |
|
}; |
|
|
|
// the names in here need to match the names in session_impl |
|
// to make the macro simpler |
|
struct all_default_values |
|
{ |
|
session_settings m_settings; |
|
proxy_settings m_proxy; |
|
#ifndef TORRENT_DISABLE_ENCRYPTION |
|
pe_settings m_pe_settings; |
|
#endif |
|
#ifndef TORRENT_DISABLE_DHT |
|
dht_settings m_dht_settings; |
|
#endif |
|
}; |
|
|
|
#define lenof(x) sizeof(x)/sizeof(x[0]) |
|
#define TORRENT_CATEGORY(name, flag, member, map) \ |
|
{ name, map, lenof(map), session:: flag , offsetof(session_impl, member), offsetof(all_default_values, member) }, |
|
|
|
session_category all_settings[] = |
|
{ |
|
TORRENT_CATEGORY("settings", save_settings, m_settings, session_settings_map) |
|
#ifndef TORRENT_DISABLE_DHT |
|
TORRENT_CATEGORY("dht", save_dht_settings, m_dht_settings, dht_settings_map) |
|
#endif |
|
TORRENT_CATEGORY("proxy", save_proxy, m_proxy, proxy_settings_map) |
|
#if TORRENT_USE_I2P |
|
// TORRENT_CATEGORY("i2p", save_i2p_proxy, m_i2p_proxy, proxy_settings_map) |
|
#endif |
|
#ifndef TORRENT_DISABLE_ENCRYPTION |
|
TORRENT_CATEGORY("encryption", save_encryption_settings, m_pe_settings, pe_settings_map) |
|
#endif |
|
}; |
|
|
|
std::pair<bencode_map_entry*, int> settings_map() |
|
{ |
|
return std::make_pair(session_settings_map, lenof(session_settings_map)); |
|
} |
|
#undef lenof |
|
|
|
#ifdef TORRENT_STATS |
|
int session_impl::logging_allocator::allocations = 0; |
|
int session_impl::logging_allocator::allocated_bytes = 0; |
|
#endif |
|
|
|
#if defined TORRENT_USE_OPENSSL && BOOST_VERSION >= 104700 && OPENSSL_VERSION_NUMBER >= 0x90812f |
|
// when running bittorrent over SSL, the SNI (server name indication) |
|
// extension is used to know which torrent the incoming connection is |
|
// trying to connect to. The 40 first bytes in the name is expected to |
|
// be the hex encoded info-hash |
|
int servername_callback(SSL *s, int *ad, void *arg) |
|
{ |
|
session_impl* ses = (session_impl*)arg; |
|
const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); |
|
|
|
if (!servername || strlen(servername) < 40) |
|
return SSL_TLSEXT_ERR_ALERT_FATAL; |
|
|
|
sha1_hash info_hash; |
|
bool valid = from_hex(servername, 40, (char*)&info_hash[0]); |
|
|
|
// the server name is not a valid hex-encoded info-hash |
|
if (!valid) |
|
return SSL_TLSEXT_ERR_ALERT_FATAL; |
|
|
|
// see if there is a torrent with this info-hash |
|
boost::shared_ptr<torrent> t = ses->find_torrent(info_hash).lock(); |
|
|
|
// if there isn't, fail |
|
if (!t) return SSL_TLSEXT_ERR_ALERT_FATAL; |
|
|
|
// if the torrent we found isn't an SSL torrent, also fail. |
|
// the torrent doesn't have an SSL context and should not allow |
|
// incoming SSL connections |
|
if (!t->is_ssl_torrent()) return SSL_TLSEXT_ERR_ALERT_FATAL; |
|
|
|
// use this torrent's certificate |
|
SSL_set_SSL_CTX(s, t->ssl_ctx()->native_handle()); |
|
|
|
return SSL_TLSEXT_ERR_OK; |
|
} |
|
#endif |
|
|
|
session_impl::session_impl(CLevelDB &swarmDb, |
|
std::pair<int, int> listen_port_range |
|
, fingerprint const& cl_fprint |
|
, char const* listen_interface |
|
, boost::uint32_t alert_mask |
|
, char const* ext_ip |
|
) |
|
: m_ipv4_peer_pool(500) |
|
#if TORRENT_USE_IPV6 |
|
, m_ipv6_peer_pool(500) |
|
#endif |
|
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR |
|
, m_send_buffers(send_buffer_size) |
|
#endif |
|
, m_files(40) |
|
, m_swarmDb(swarmDb) |
|
, m_io_service() |
|
#ifdef TORRENT_USE_OPENSSL |
|
, m_ssl_ctx(m_io_service, asio::ssl::context::sslv23) |
|
#endif |
|
, m_alerts(m_settings.alert_queue_size, alert_mask) |
|
, m_disk_thread(m_io_service, boost::bind(&session_impl::on_disk_queue, this), m_files) |
|
, m_half_open(m_io_service) |
|
, m_download_rate(peer_connection::download_channel) |
|
#ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT |
|
, m_upload_rate(peer_connection::upload_channel, true) |
|
#else |
|
, m_upload_rate(peer_connection::upload_channel) |
|
#endif |
|
, m_tracker_manager(*this, m_proxy) |
|
, m_num_active_downloading(0) |
|
, m_num_active_finished(0) |
|
, m_listen_port_retries(listen_port_range.second - listen_port_range.first) |
|
#if TORRENT_USE_I2P |
|
, m_i2p_conn(m_io_service) |
|
#endif |
|
, m_abort(false) |
|
, m_paused(false) |
|
, m_allowed_upload_slots(8) |
|
, m_num_unchoked(0) |
|
, m_unchoke_time_scaler(0) |
|
, m_auto_manage_time_scaler(0) |
|
, m_optimistic_unchoke_time_scaler(0) |
|
, m_disconnect_time_scaler(90) |
|
, m_auto_scrape_time_scaler(180) |
|
, m_next_explicit_cache_torrent(0) |
|
, m_cache_rotation_timer(0) |
|
, m_peak_up_rate(0) |
|
, m_peak_down_rate(0) |
|
, m_incoming_connection(false) |
|
, m_created(time_now_hires()) |
|
, m_last_tick(m_created) |
|
, m_last_second_tick(m_created - milliseconds(900)) |
|
, m_last_disk_performance_warning(min_time()) |
|
, m_last_disk_queue_performance_warning(min_time()) |
|
, m_last_choke(m_created) |
|
, m_next_rss_update(min_time()) |
|
#ifndef TORRENT_DISABLE_DHT |
|
, m_dht_announce_timer(m_io_service) |
|
, m_dht_interval_update_torrents(0) |
|
#endif |
|
, m_external_udp_port(0) |
|
, m_udp_socket(m_io_service, m_half_open) |
|
, m_utp_socket_manager(m_settings, m_udp_socket |
|
, boost::bind(&session_impl::incoming_connection, this, _1)) |
|
, m_boost_connections(0) |
|
, m_timer(m_io_service) |
|
, m_lsd_announce_timer(m_io_service) |
|
, m_host_resolver(m_io_service) |
|
, m_current_connect_attempts(0) |
|
, m_tick_residual(0) |
|
, m_non_filtered_torrents(0) |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
, m_logpath(".") |
|
#endif |
|
#ifndef TORRENT_DISABLE_GEO_IP |
|
, m_asnum_db(0) |
|
, m_country_db(0) |
|
#endif |
|
, m_total_failed_bytes(0) |
|
, m_total_redundant_bytes(0) |
|
, m_pending_auto_manage(false) |
|
, m_need_auto_manage(false) |
|
#if (defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS) && defined BOOST_HAS_PTHREADS |
|
, m_network_thread(0) |
|
#endif |
|
{ |
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS |
|
m_posting_torrent_updates = false; |
|
#endif |
|
|
|
memset(m_redundant_bytes, 0, sizeof(m_redundant_bytes)); |
|
m_udp_socket.set_rate_limit(m_settings.dht_upload_rate_limit); |
|
|
|
m_udp_socket.subscribe(&m_tracker_manager); |
|
m_udp_socket.subscribe(&m_utp_socket_manager); |
|
m_udp_socket.subscribe(this); |
|
|
|
m_disk_queues[0] = 0; |
|
m_disk_queues[1] = 0; |
|
|
|
#ifdef TORRENT_REQUEST_LOGGING |
|
char log_filename[200]; |
|
#ifdef TORRENT_WINDOWS |
|
const int pid = GetCurrentProcessId(); |
|
#else |
|
const int pid = getpid(); |
|
#endif |
|
snprintf(log_filename, sizeof(log_filename), "requests-%d.log", pid); |
|
m_request_log = fopen(log_filename, "w+"); |
|
if (m_request_log == 0) |
|
{ |
|
fprintf(stderr, "failed to open request log file: (%d) %s\n", errno, strerror(errno)); |
|
} |
|
#endif |
|
|
|
error_code ec; |
|
if (!listen_interface) listen_interface = "0.0.0.0"; |
|
m_listen_interface = tcp::endpoint(address::from_string(listen_interface, ec), listen_port_range.first); |
|
TORRENT_ASSERT_VAL(!ec, ec); |
|
|
|
if (ext_ip) { |
|
m_external_ip.cast_vote(address::from_string(ext_ip), source_router, address()); |
|
} |
|
|
|
// ---- generate a peer id ---- |
|
static seed_random_generator seeder; |
|
|
|
m_key = random() + (random() << 15) + (random() << 30); |
|
std::string print = cl_fprint.to_string(); |
|
TORRENT_ASSERT_VAL(print.length() <= 20, print.length()); |
|
|
|
// the client's fingerprint |
|
std::copy( |
|
print.begin() |
|
, print.begin() + print.length() |
|
, m_peer_id.begin()); |
|
|
|
url_random((char*)&m_peer_id[print.length()], (char*)&m_peer_id[0] + 20); |
|
|
|
update_rate_settings(); |
|
update_connections_limit(); |
|
update_unchoke_limit(); |
|
} |
|
|
|
void session_impl::start_session() |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
m_logger = create_log("main_session", listen_port(), false); |
|
session_log("session_impl::start_session log created"); |
|
#endif |
|
|
|
error_code ec; |
|
#ifdef TORRENT_USE_OPENSSL |
|
m_ssl_ctx.set_verify_mode(asio::ssl::context::verify_none, ec); |
|
#if BOOST_VERSION >= 104700 |
|
#if OPENSSL_VERSION_NUMBER >= 0x90812f |
|
SSL_CTX_set_tlsext_servername_callback(m_ssl_ctx.native_handle(), servername_callback); |
|
SSL_CTX_set_tlsext_servername_arg(m_ssl_ctx.native_handle(), this); |
|
#endif // OPENSSL_VERSION_NUMBER |
|
#endif // BOOST_VERSION |
|
#endif |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
m_next_dht_torrent = m_torrents.begin(); |
|
#endif |
|
m_next_lsd_torrent = m_torrents.begin(); |
|
m_next_connect_torrent = m_torrents.begin(); |
|
m_next_disk_peer = m_connections.begin(); |
|
|
|
m_tcp_mapping[0] = -1; |
|
m_tcp_mapping[1] = -1; |
|
m_twister_tcp_mapping[0] = -1; |
|
m_twister_tcp_mapping[1] = -1; |
|
m_udp_mapping[0] = -1; |
|
m_udp_mapping[1] = -1; |
|
#ifdef TORRENT_USE_OPENSSL |
|
m_ssl_mapping[0] = -1; |
|
m_ssl_mapping[1] = -1; |
|
#endif |
|
#ifdef WIN32 |
|
// windows XP has a limit on the number of |
|
// simultaneous half-open TCP connections |
|
// here's a table: |
|
|
|
// windows version half-open connections limit |
|
// --------------------- --------------------------- |
|
// XP sp1 and earlier infinite |
|
// earlier than vista 8 |
|
// vista sp1 and earlier 5 |
|
// vista sp2 and later infinite |
|
|
|
// windows release version number |
|
// ----------------------------------- -------------- |
|
// Windows 7 6.1 |
|
// Windows Server 2008 R2 6.1 |
|
// Windows Server 2008 6.0 |
|
// Windows Vista 6.0 |
|
// Windows Server 2003 R2 5.2 |
|
// Windows Home Server 5.2 |
|
// Windows Server 2003 5.2 |
|
// Windows XP Professional x64 Edition 5.2 |
|
// Windows XP 5.1 |
|
// Windows 2000 5.0 |
|
|
|
OSVERSIONINFOEX osv; |
|
memset(&osv, 0, sizeof(osv)); |
|
osv.dwOSVersionInfoSize = sizeof(osv); |
|
GetVersionEx((OSVERSIONINFO*)&osv); |
|
|
|
// the low two bytes of windows_version is the actual |
|
// version. |
|
boost::uint32_t windows_version |
|
= ((osv.dwMajorVersion & 0xff) << 16) |
|
| ((osv.dwMinorVersion & 0xff) << 8) |
|
| (osv.wServicePackMajor & 0xff); |
|
|
|
// this is the format of windows_version |
|
// xx xx xx |
|
// | | | |
|
// | | + service pack version |
|
// | + minor version |
|
// + major version |
|
|
|
// the least significant byte is the major version |
|
// and the most significant one is the minor version |
|
if (windows_version >= 0x060100) |
|
{ |
|
// windows 7 and up doesn't have a half-open limit |
|
m_half_open.limit(0); |
|
} |
|
else if (windows_version >= 0x060002) |
|
{ |
|
// on vista SP 2 and up, there's no limit |
|
m_half_open.limit(0); |
|
} |
|
else if (windows_version >= 0x060000) |
|
{ |
|
// on vista the limit is 5 (in home edition) |
|
m_half_open.limit(4); |
|
} |
|
else if (windows_version >= 0x050102) |
|
{ |
|
// on XP SP2 the limit is 10 |
|
m_half_open.limit(9); |
|
} |
|
else |
|
{ |
|
// before XP SP2, there was no limit |
|
m_half_open.limit(0); |
|
} |
|
m_settings.half_open_limit = m_half_open.limit(); |
|
#endif |
|
|
|
m_bandwidth_channel[peer_connection::download_channel] = &m_download_channel; |
|
m_bandwidth_channel[peer_connection::upload_channel] = &m_upload_channel; |
|
|
|
#ifdef TORRENT_UPNP_LOGGING |
|
m_upnp_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); |
|
#endif |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
|
|
char tmp[300]; |
|
snprintf(tmp, sizeof(tmp), "libtorrent configuration: %s\n" |
|
"libtorrent version: %s\n" |
|
"libtorrent revision: %s\n\n" |
|
, TORRENT_CFG_STRING |
|
, LIBTORRENT_VERSION |
|
, LIBTORRENT_REVISION); |
|
(*m_logger) << tmp; |
|
|
|
logger& l = *m_logger; |
|
|
|
int temp = 0; |
|
int prev_size = 0; |
|
|
|
PRINT_SIZEOF(announce_entry) |
|
PRINT_OFFSETOF(announce_entry, url) |
|
PRINT_OFFSETOF(announce_entry, trackerid) |
|
PRINT_OFFSETOF(announce_entry, message) |
|
PRINT_OFFSETOF(announce_entry, last_error) |
|
PRINT_OFFSETOF(announce_entry, next_announce) |
|
PRINT_OFFSETOF(announce_entry, min_announce) |
|
PRINT_OFFSETOF(announce_entry, tier) |
|
PRINT_OFFSETOF(announce_entry, fail_limit) |
|
PRINT_OFFSETOF_END(announce_entry) |
|
|
|
PRINT_SIZEOF(torrent_info) |
|
PRINT_OFFSETOF(torrent_info, m_refs) |
|
PRINT_OFFSETOF(torrent_info, m_merkle_first_leaf) |
|
PRINT_OFFSETOF(torrent_info, m_files) |
|
PRINT_OFFSETOF(torrent_info, m_orig_files) |
|
PRINT_OFFSETOF(torrent_info, m_urls) |
|
PRINT_OFFSETOF(torrent_info, m_web_seeds) |
|
PRINT_OFFSETOF(torrent_info, m_nodes) |
|
PRINT_OFFSETOF(torrent_info, m_merkle_tree) |
|
PRINT_OFFSETOF(torrent_info, m_info_section) |
|
PRINT_OFFSETOF(torrent_info, m_piece_hashes) |
|
PRINT_OFFSETOF(torrent_info, m_comment) |
|
PRINT_OFFSETOF(torrent_info, m_created_by) |
|
#ifdef TORRENT_USE_OPENSSL |
|
PRINT_OFFSETOF(torrent_info, m_ssl_root_cert) |
|
#endif |
|
PRINT_OFFSETOF(torrent_info, m_info_dict) |
|
PRINT_OFFSETOF(torrent_info, m_creation_date) |
|
PRINT_OFFSETOF(torrent_info, m_info_hash) |
|
PRINT_OFFSETOF_END(torrent_info) |
|
|
|
PRINT_SIZEOF(union_endpoint) |
|
PRINT_SIZEOF(request_callback) |
|
PRINT_SIZEOF(stat) |
|
PRINT_SIZEOF(bandwidth_channel) |
|
PRINT_SIZEOF(policy) |
|
(*m_logger) << "sizeof(utp_socket_impl): " << socket_impl_size() << "\n"; |
|
|
|
PRINT_SIZEOF(file_entry) |
|
PRINT_SIZEOF(internal_file_entry) |
|
PRINT_OFFSETOF(internal_file_entry, name) |
|
PRINT_OFFSETOF(internal_file_entry, path_index) |
|
PRINT_OFFSETOF_END(internal_file_entry) |
|
|
|
PRINT_SIZEOF(file_storage) |
|
PRINT_OFFSETOF(file_storage, m_files) |
|
PRINT_OFFSETOF(file_storage, m_file_hashes) |
|
PRINT_OFFSETOF(file_storage, m_symlinks) |
|
PRINT_OFFSETOF(file_storage, m_mtime) |
|
PRINT_OFFSETOF(file_storage, m_file_base) |
|
PRINT_OFFSETOF(file_storage, m_paths) |
|
PRINT_OFFSETOF(file_storage, m_name) |
|
PRINT_OFFSETOF(file_storage, m_total_size) |
|
PRINT_OFFSETOF(file_storage, m_num_pieces) |
|
PRINT_OFFSETOF(file_storage, m_piece_length) |
|
PRINT_OFFSETOF_END(file_storage) |
|
|
|
// PRINT_SIZEOF(stat_channel) |
|
// PRINT_OFFSETOF(stat_channel, m_counter) |
|
// PRINT_OFFSETOF(stat_channel, m_average) |
|
// PRINT_OFFSETOF(stat_channel, m_total_counter) |
|
|
|
torrent::print_size(*m_logger); |
|
|
|
PRINT_SIZEOF(peer_connection) |
|
PRINT_SIZEOF(bt_peer_connection) |
|
PRINT_SIZEOF(address) |
|
PRINT_SIZEOF(address_v4) |
|
PRINT_SIZEOF(address_v4::bytes_type) |
|
#if TORRENT_USE_IPV6 |
|
PRINT_SIZEOF(address_v6) |
|
PRINT_SIZEOF(address_v6::bytes_type) |
|
#endif |
|
PRINT_SIZEOF(void*) |
|
#ifndef TORRENT_DISABLE_DHT |
|
PRINT_SIZEOF(dht::node_entry) |
|
#endif |
|
|
|
PRINT_SIZEOF(policy::peer) |
|
PRINT_OFFSETOF(policy::peer, prev_amount_upload) |
|
PRINT_OFFSETOF(policy::peer, prev_amount_download) |
|
PRINT_OFFSETOF(policy::peer, connection) |
|
#ifndef TORRENT_DISABLE_GEO_IP |
|
#ifdef TORRENT_DEBUG |
|
PRINT_OFFSETOF(policy::peer, inet_as_num) |
|
#endif |
|
PRINT_OFFSETOF(policy::peer, inet_as) |
|
#endif |
|
PRINT_OFFSETOF(policy::peer, last_optimistically_unchoked) |
|
PRINT_OFFSETOF(policy::peer, last_connected) |
|
PRINT_OFFSETOF(policy::peer, port) |
|
PRINT_OFFSETOF(policy::peer, upload_rate_limit) |
|
PRINT_OFFSETOF(policy::peer, download_rate_limit) |
|
PRINT_OFFSETOF(policy::peer, hashfails) |
|
PRINT_OFFSETOF_END(policy::peer) |
|
|
|
PRINT_SIZEOF(policy::ipv4_peer) |
|
#if TORRENT_USE_IPV6 |
|
PRINT_SIZEOF(policy::ipv6_peer) |
|
#endif |
|
|
|
PRINT_SIZEOF(udp_socket) |
|
PRINT_OFFSETOF(udp_socket, m_ipv4_sock) |
|
PRINT_OFFSETOF(udp_socket, m_buf) |
|
#if TORRENT_USE_IPV6 |
|
PRINT_OFFSETOF(udp_socket, m_ipv6_sock) |
|
#endif |
|
PRINT_OFFSETOF(udp_socket, m_bind_port) |
|
PRINT_OFFSETOF(udp_socket, m_v4_outstanding) |
|
#if TORRENT_USE_IPV6 |
|
PRINT_OFFSETOF(udp_socket, m_v6_outstanding) |
|
#endif |
|
PRINT_OFFSETOF(udp_socket, m_socks5_sock) |
|
PRINT_OFFSETOF(udp_socket, m_connection_ticket) |
|
PRINT_OFFSETOF(udp_socket, m_proxy_settings) |
|
#ifndef _MSC_VER |
|
PRINT_OFFSETOF(udp_socket, m_cc) |
|
#endif |
|
PRINT_OFFSETOF(udp_socket, m_resolver) |
|
PRINT_OFFSETOF(udp_socket, m_tmp_buf) |
|
PRINT_OFFSETOF(udp_socket, m_queue_packets) |
|
PRINT_OFFSETOF(udp_socket, m_tunnel_packets) |
|
PRINT_OFFSETOF(udp_socket, m_abort) |
|
PRINT_OFFSETOF(udp_socket, m_proxy_addr) |
|
PRINT_OFFSETOF(udp_socket, m_queue) |
|
PRINT_OFFSETOF(udp_socket, m_outstanding_ops) |
|
#ifdef TORRENT_DEBUG |
|
PRINT_OFFSETOF(udp_socket, m_started) |
|
PRINT_OFFSETOF(udp_socket, m_magic) |
|
PRINT_OFFSETOF(udp_socket, m_outstanding_when_aborted) |
|
#endif |
|
PRINT_OFFSETOF_END(udp_socket) |
|
|
|
PRINT_SIZEOF(tracker_connection) |
|
PRINT_SIZEOF(http_tracker_connection) |
|
|
|
PRINT_SIZEOF(udp_tracker_connection) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_refs) |
|
|
|
PRINT_OFFSETOF(udp_tracker_connection, m_start_time) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_read_time) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_timeout) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_completion_timeout) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_read_timeout) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_mutex) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_requester) |
|
#ifndef _MSC_VER |
|
PRINT_OFFSETOF(udp_tracker_connection, m_man) |
|
#endif |
|
|
|
PRINT_OFFSETOF(udp_tracker_connection, m_req) |
|
|
|
PRINT_OFFSETOF(udp_tracker_connection, m_abort) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_hostname) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_target) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_endpoints) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_transaction_id) |
|
#ifndef _MSC_VER |
|
PRINT_OFFSETOF(udp_tracker_connection, m_ses) |
|
#endif |
|
PRINT_OFFSETOF(udp_tracker_connection, m_attempts) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_state) |
|
PRINT_OFFSETOF(udp_tracker_connection, m_proxy) |
|
PRINT_OFFSETOF_END(udp_tracker_connection) |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
PRINT_SIZEOF(dht::find_data_observer) |
|
PRINT_SIZEOF(dht::announce_observer) |
|
PRINT_SIZEOF(dht::null_observer) |
|
#endif |
|
#undef PRINT_OFFSETOF_END |
|
#undef PRINT_OFFSETOF |
|
#undef PRINT_SIZEOF |
|
|
|
#endif |
|
|
|
#ifdef TORRENT_STATS |
|
|
|
m_stats_logger = 0; |
|
m_log_seq = 0; |
|
m_stats_logging_enabled = true; |
|
|
|
memset(&m_last_cache_status, 0, sizeof(m_last_cache_status)); |
|
get_vm_stats(&m_last_vm_stat); |
|
|
|
m_last_failed = 0; |
|
m_last_redundant = 0; |
|
m_last_uploaded = 0; |
|
m_last_downloaded = 0; |
|
get_thread_cpu_usage(&m_network_thread_cpu_usage); |
|
|
|
reset_stat_counters(); |
|
rotate_stats_log(); |
|
#endif |
|
#ifdef TORRENT_DISK_STATS |
|
m_buffer_usage_logger.open("buffer_stats.log", std::ios::trunc); |
|
m_buffer_allocations = 0; |
|
#endif |
|
|
|
#if defined TORRENT_BSD || defined TORRENT_LINUX |
|
// ---- auto-cap open files ---- |
|
|
|
struct rlimit rl; |
|
if (getrlimit(RLIMIT_NOFILE, &rl) == 0) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log(" max number of open files: %d", rl.rlim_cur); |
|
#endif |
|
|
|
// deduct some margin for epoll/kqueue, log files, |
|
// futexes, shared objects etc. |
|
rl.rlim_cur -= 20; |
|
|
|
// 80% of the available file descriptors should go |
|
m_settings.connections_limit = (std::min)(m_settings.connections_limit |
|
, int(rl.rlim_cur * 8 / 10)); |
|
// 20% goes towards regular files |
|
m_files.resize((std::min)(m_files.size_limit(), int(rl.rlim_cur * 2 / 10))); |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
(*m_logger) << time_now_string() << " max connections: " << m_settings.connections_limit << "\n"; |
|
(*m_logger) << time_now_string() << " max files: " << m_files.size_limit() << "\n"; |
|
#endif |
|
} |
|
#endif // TORRENT_BSD || TORRENT_LINUX |
|
|
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log(" generated peer ID: %s", m_peer_id.to_string().c_str()); |
|
#endif |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log(" spawning network thread"); |
|
#endif |
|
m_thread.reset(new thread(boost::bind(&session_impl::main_thread, this))); |
|
} |
|
|
|
#ifdef TORRENT_STATS |
|
void session_impl::rotate_stats_log() |
|
{ |
|
if (m_stats_logger) |
|
{ |
|
++m_log_seq; |
|
fclose(m_stats_logger); |
|
} |
|
|
|
// make these cumulative for easier reading of graphs |
|
// reset them every time the log is rotated though, |
|
// to make them cumulative per one-hour graph |
|
m_error_peers = 0; |
|
m_disconnected_peers = 0; |
|
m_eof_peers = 0; |
|
m_connreset_peers = 0; |
|
m_connrefused_peers = 0; |
|
m_connaborted_peers = 0; |
|
m_perm_peers = 0; |
|
m_buffer_peers = 0; |
|
m_unreachable_peers = 0; |
|
m_broken_pipe_peers = 0; |
|
m_addrinuse_peers = 0; |
|
m_no_access_peers = 0; |
|
m_invalid_arg_peers = 0; |
|
m_aborted_peers = 0; |
|
m_error_incoming_peers = 0; |
|
m_error_outgoing_peers = 0; |
|
m_error_rc4_peers = 0; |
|
m_error_encrypted_peers = 0; |
|
m_error_tcp_peers = 0; |
|
m_error_utp_peers = 0; |
|
m_connect_timeouts = 0; |
|
m_uninteresting_peers = 0; |
|
m_transport_timeout_peers = 0; |
|
m_timeout_peers = 0; |
|
m_no_memory_peers = 0; |
|
m_too_many_peers = 0; |
|
|
|
error_code ec; |
|
char filename[100]; |
|
create_directory("session_stats", ec); |
|
#ifdef TORRENT_WINDOWS |
|
const int pid = GetCurrentProcessId(); |
|
#else |
|
const int pid = getpid(); |
|
#endif |
|
snprintf(filename, sizeof(filename), "session_stats/%d.%04d.log", pid, m_log_seq); |
|
m_stats_logger = fopen(filename, "w+"); |
|
if (m_stats_logger == 0) |
|
{ |
|
fprintf(stderr, "Failed to create session stats log file \"%s\": (%d) %s\n" |
|
, filename, errno, strerror(errno)); |
|
return; |
|
} |
|
m_last_log_rotation = time_now(); |
|
|
|
fputs("second:uploaded bytes:downloaded bytes:downloading torrents:seeding torrents" |
|
":peers:connecting peers:disk block buffers:num list peers" |
|
":peer allocations:peer storage bytes" |
|
":checking torrents" |
|
":stopped torrents" |
|
":upload-only torrents" |
|
":queued seed torrents" |
|
":queued download torrents" |
|
":peers bw-up:peers bw-down:peers disk-up:peers disk-down" |
|
":upload rate:download rate:disk write queued bytes" |
|
":peers down 0:peers down 0-2:peers down 2-5:peers down 5-10:peers down 10-50" |
|
":peers down 50-100:peers down 100-" |
|
":peers up 0:peers up 0-2:peers up 2-5:peers up 5-10:peers up 10-50:peers up 50-100" |
|
":peers up 100-:error peers" |
|
":peers down interesting:peers down unchoked:peers down requests" |
|
":peers up interested:peers up unchoked:peers up requests" |
|
":peer disconnects:peers eof:peers connection reset" |
|
":outstanding requests:outstanding end-game requests" |
|
":outstanding writing blocks" |
|
":end game piece picker blocks" |
|
":piece picker blocks" |
|
":piece picks" |
|
":reject piece picks" |
|
":unchoke piece picks" |
|
":incoming redundant piece picks" |
|
":incoming piece picks" |
|
":end game piece picks" |
|
":snubbed piece picks" |
|
":connect timeouts" |
|
":uninteresting peers disconnect" |
|
":timeout peers" |
|
":% failed payload bytes" |
|
":% wasted payload bytes" |
|
":% protocol bytes" |
|
":disk read time" |
|
":disk write time" |
|
":disk queue time" |
|
":disk queue size" |
|
":disk queued bytes" |
|
":read cache hits" |
|
":disk block read" |
|
":disk block written" |
|
":failed bytes" |
|
":redundant bytes" |
|
":error torrents" |
|
":read disk cache size" |
|
":disk cache size" |
|
":disk buffer allocations" |
|
":disk hash time" |
|
":disk job time" |
|
":disk sort time" |
|
":connection attempts" |
|
":banned peers" |
|
":banned for hash failure" |
|
":cache size" |
|
":max connections" |
|
":connect candidates" |
|
":disk queue limit" |
|
":disk queue low watermark" |
|
":% read time" |
|
":% write time" |
|
":% hash time" |
|
":% sort time" |
|
":disk read back" |
|
":% read back" |
|
":disk read queue size" |
|
":tick interval" |
|
":tick residual" |
|
":max unchoked" |
|
":read job queue size limit" |
|
":smooth upload rate" |
|
":smooth download rate" |
|
":num end-game peers" |
|
":TCP up rate" |
|
":TCP down rate" |
|
":TCP up limit" |
|
":TCP down limit" |
|
":uTP up rate" |
|
":uTP down rate" |
|
":uTP peak send delay" |
|
":uTP avg send delay" |
|
":uTP peak recv delay" |
|
":uTP avg recv delay" |
|
":read ops/s" |
|
":write ops/s" |
|
":active resident pages" |
|
":inactive resident pages" |
|
":pinned resident pages" |
|
":free pages" |
|
":pageins" |
|
":pageouts" |
|
":page faults" |
|
":smooth read ops/s" |
|
":smooth write ops/s" |
|
":pending reading bytes" |
|
":read_counter" |
|
":write_counter" |
|
":tick_counter" |
|
":lsd_counter" |
|
":lsd_peer_counter" |
|
":udp_counter" |
|
":accept_counter" |
|
":disk_queue_counter" |
|
":disk_read_counter" |
|
":disk_write_counter" |
|
":up 8:up 16:up 32:up 64:up 128:up 256:up 512:up 1024:up 2048:up 4096:up 8192:up 16384:up 32768:up 65536:up 131072:up 262144:up 524288:up 1048576" |
|
":down 8:down 16:down 32:down 64:down 128:down 256:down 512:down 1024:down 2048:down 4096:down 8192:down 16384:down 32768:down 65536:down 131072:down 262144:down 524288:down 1048576" |
|
":network thread system time" |
|
":network thread user+system time" |
|
|
|
":redundant timed-out" |
|
":redundant cancelled" |
|
":redundant unknown" |
|
":redundant seed" |
|
":redundant end-game" |
|
":redundant closing" |
|
":no memory peer errors" |
|
":too many peers" |
|
":transport timeout peers" |
|
":uTP idle" |
|
":uTP syn-sent" |
|
":uTP connected" |
|
":uTP fin-sent" |
|
":uTP close-wait" |
|
|
|
":tcp peers" |
|
":utp peers" |
|
|
|
":connection refused peers" |
|
":connection aborted peers" |
|
":permission denied peers" |
|
":no buffer peers" |
|
":host unreachable peers" |
|
":broken pipe peers" |
|
":address in use peers" |
|
":access denied peers" |
|
":invalid argument peers" |
|
":operation aborted peers" |
|
|
|
":error incoming peers" |
|
":error outgoing peers" |
|
":error rc4 peers" |
|
":error encrypted peers" |
|
":error tcp peers" |
|
":error utp peers" |
|
|
|
":total peers" |
|
":pending incoming block requests" |
|
":average pending incoming block requests" |
|
|
|
":torrents want more peers" |
|
":average peers per limit" |
|
|
|
":piece requests" |
|
":max piece requests" |
|
":invalid piece requests" |
|
":choked piece requests" |
|
":cancelled piece requests" |
|
":piece rejects" |
|
|
|
":peers up send buffer" |
|
|
|
"\n\n", m_stats_logger); |
|
} |
|
#endif |
|
|
|
void session_impl::trigger_auto_manage() |
|
{ |
|
if (m_pending_auto_manage || m_abort) return; |
|
|
|
m_pending_auto_manage = true; |
|
m_need_auto_manage = true; |
|
m_io_service.post(boost::bind(&session_impl::on_trigger_auto_manage, this)); |
|
} |
|
|
|
void session_impl::on_trigger_auto_manage() |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
assert(m_pending_auto_manage); |
|
m_pending_auto_manage = false; |
|
if (!m_need_auto_manage) return; |
|
recalculate_auto_managed_torrents(); |
|
} |
|
|
|
void session_impl::update_dht_announce_interval() |
|
{ |
|
#ifndef TORRENT_DISABLE_DHT |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_dht_announce"); |
|
#endif |
|
m_dht_interval_update_torrents = m_torrents.size(); |
|
error_code ec; |
|
int delay = (std::max)(m_settings.dht_announce_interval |
|
/ (std::max)(int(m_torrents.size()), 1), 1); |
|
m_dht_announce_timer.expires_from_now(seconds(delay), ec); |
|
m_dht_announce_timer.async_wait( |
|
boost::bind(&session_impl::on_dht_announce, this, _1)); |
|
TORRENT_ASSERT(!ec); |
|
#endif |
|
} |
|
|
|
void session_impl::init() |
|
{ |
|
#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING |
|
session_log(" *** session thread init"); |
|
#endif |
|
|
|
// this is where we should set up all async operations. This |
|
// is called from within the network thread as opposed to the |
|
// constructor which is called from the main thread |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
async_inc_threads(); |
|
add_outstanding_async("session_impl::on_tick"); |
|
#endif |
|
error_code ec; |
|
m_io_service.post(boost::bind(&session_impl::on_tick, this, ec)); |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_lsd_announce"); |
|
#endif |
|
int delay = (std::max)(m_settings.local_service_announce_interval |
|
/ (std::max)(int(m_torrents.size()), 1), 1); |
|
m_lsd_announce_timer.expires_from_now(seconds(delay), ec); |
|
m_lsd_announce_timer.async_wait( |
|
boost::bind(&session_impl::on_lsd_announce, this, _1)); |
|
TORRENT_ASSERT(!ec); |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
update_dht_announce_interval(); |
|
#endif |
|
|
|
#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING |
|
session_log(" open listen port"); |
|
#endif |
|
// no reuse_address and allow system defined port |
|
open_listen_port(0, ec); |
|
#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING |
|
session_log(" done starting session"); |
|
#endif |
|
} |
|
|
|
void session_impl::save_state(entry* eptr, boost::uint32_t flags) const |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
entry& e = *eptr; |
|
|
|
all_default_values def; |
|
|
|
for (int i = 0; i < int(sizeof(all_settings)/sizeof(all_settings[0])); ++i) |
|
{ |
|
session_category const& c = all_settings[i]; |
|
if ((flags & c.flag) == 0) continue; |
|
save_struct(e[c.name], reinterpret_cast<char const*>(this) + c.offset |
|
, c.map, c.num_entries, reinterpret_cast<char const*>(&def) + c.default_offset); |
|
} |
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_dht && (flags & session::save_dht_state)) |
|
{ |
|
e["dht state"] = m_dht->state(); |
|
} |
|
#endif |
|
|
|
#if TORRENT_USE_I2P |
|
if (flags & session::save_i2p_proxy) |
|
{ |
|
save_struct(e["i2p"], &i2p_proxy(), proxy_settings_map |
|
, sizeof(proxy_settings_map)/sizeof(proxy_settings_map[0]) |
|
, &def.m_proxy); |
|
} |
|
#endif |
|
#ifndef TORRENT_DISABLE_GEO_IP |
|
if (flags & session::save_as_map) |
|
{ |
|
entry::dictionary_type& as_map = e["AS map"].dict(); |
|
char buf[10]; |
|
for (std::map<int, int>::const_iterator i = m_as_peak.begin() |
|
, end(m_as_peak.end()); i != end; ++i) |
|
{ |
|
if (i->second == 0) continue; |
|
sprintf(buf, "%05d", i->first); |
|
as_map[buf] = i->second; |
|
} |
|
} |
|
#endif |
|
|
|
if (flags & session::save_feeds) |
|
{ |
|
entry::list_type& feeds = e["feeds"].list(); |
|
for (std::vector<boost::shared_ptr<feed> >::const_iterator i = |
|
m_feeds.begin(), end(m_feeds.end()); i != end; ++i) |
|
{ |
|
feeds.push_back(entry()); |
|
(*i)->save_state(feeds.back()); |
|
} |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS |
|
for (ses_extension_list_t::const_iterator i = m_ses_extensions.begin() |
|
, end(m_ses_extensions.end()); i != end; ++i) |
|
{ |
|
TORRENT_TRY { |
|
(*i)->save_state(*eptr); |
|
} TORRENT_CATCH(std::exception&) {} |
|
} |
|
#endif |
|
} |
|
|
|
void session_impl::set_proxy(proxy_settings const& s) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
m_proxy = s; |
|
// in case we just set a socks proxy, we might have to |
|
// open the socks incoming connection |
|
if (!m_socks_listen_socket) open_new_incoming_socks_connection(); |
|
m_udp_socket.set_proxy_settings(m_proxy); |
|
} |
|
|
|
void session_impl::load_state(lazy_entry const* e) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
lazy_entry const* settings; |
|
|
|
if (e->type() != lazy_entry::dict_t) return; |
|
|
|
for (int i = 0; i < int(sizeof(all_settings)/sizeof(all_settings[0])); ++i) |
|
{ |
|
session_category const& c = all_settings[i]; |
|
settings = e->dict_find_dict(c.name); |
|
if (!settings) continue; |
|
load_struct(*settings, reinterpret_cast<char*>(this) + c.offset, c.map, c.num_entries); |
|
} |
|
|
|
update_rate_settings(); |
|
update_connections_limit(); |
|
update_unchoke_limit(); |
|
m_alerts.set_alert_queue_size_limit(m_settings.alert_queue_size); |
|
|
|
// in case we just set a socks proxy, we might have to |
|
// open the socks incoming connection |
|
if (!m_socks_listen_socket) open_new_incoming_socks_connection(); |
|
m_udp_socket.set_proxy_settings(m_proxy); |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
settings = e->dict_find_dict("dht state"); |
|
if (settings) |
|
{ |
|
m_dht_state = *settings; |
|
} |
|
#endif |
|
|
|
#if TORRENT_USE_I2P |
|
settings = e->dict_find_dict("i2p"); |
|
if (settings) |
|
{ |
|
proxy_settings s; |
|
load_struct(*settings, &s, proxy_settings_map |
|
, sizeof(proxy_settings_map)/sizeof(proxy_settings_map[0])); |
|
set_i2p_proxy(s); |
|
} |
|
#endif |
|
#ifndef TORRENT_DISABLE_GEO_IP |
|
settings = e->dict_find_dict("AS map"); |
|
if (settings) |
|
{ |
|
for (int i = 0; i < settings->dict_size(); ++i) |
|
{ |
|
std::pair<std::string, lazy_entry const*> item = settings->dict_at(i); |
|
int as_num = atoi(item.first.c_str()); |
|
if (item.second->type() != lazy_entry::int_t || item.second->int_value() == 0) continue; |
|
int& peak = m_as_peak[as_num]; |
|
if (peak < item.second->int_value()) peak = item.second->int_value(); |
|
} |
|
} |
|
#endif |
|
|
|
if (m_settings.connection_speed < 0) m_settings.connection_speed = 200; |
|
|
|
update_disk_thread_settings(); |
|
|
|
settings = e->dict_find_list("feeds"); |
|
if (settings) |
|
{ |
|
m_feeds.reserve(settings->list_size()); |
|
for (int i = 0; i < settings->list_size(); ++i) |
|
{ |
|
if (settings->list_at(i)->type() != lazy_entry::dict_t) continue; |
|
boost::shared_ptr<feed> f(new_feed(*this, feed_settings())); |
|
f->load_state(*settings->list_at(i)); |
|
f->update_feed(); |
|
m_feeds.push_back(f); |
|
} |
|
update_rss_feeds(); |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS |
|
for (ses_extension_list_t::iterator i = m_ses_extensions.begin() |
|
, end(m_ses_extensions.end()); i != end; ++i) |
|
{ |
|
TORRENT_TRY { |
|
(*i)->load_state(*e); |
|
} TORRENT_CATCH(std::exception&) {} |
|
} |
|
#endif |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_GEO_IP |
|
namespace |
|
{ |
|
struct free_ptr |
|
{ |
|
void* ptr_; |
|
free_ptr(void* p): ptr_(p) {} |
|
~free_ptr() { free(ptr_); } |
|
}; |
|
} |
|
|
|
char const* session_impl::country_for_ip(address const& a) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (!a.is_v4() || m_country_db == 0) return 0; |
|
return GeoIP_country_code_by_ipnum(m_country_db, a.to_v4().to_ulong()); |
|
} |
|
|
|
int session_impl::as_for_ip(address const& a) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (!a.is_v4() || m_asnum_db == 0) return 0; |
|
char* name = GeoIP_name_by_ipnum(m_asnum_db, a.to_v4().to_ulong()); |
|
if (name == 0) return 0; |
|
free_ptr p(name); |
|
// GeoIP returns the name as AS??? where ? is the AS-number |
|
return atoi(name + 2); |
|
} |
|
|
|
std::string session_impl::as_name_for_ip(address const& a) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (!a.is_v4() || m_asnum_db == 0) return std::string(); |
|
char* name = GeoIP_name_by_ipnum(m_asnum_db, a.to_v4().to_ulong()); |
|
if (name == 0) return std::string(); |
|
free_ptr p(name); |
|
char* tmp = std::strchr(name, ' '); |
|
if (tmp == 0) return std::string(); |
|
return tmp + 1; |
|
} |
|
|
|
std::pair<const int, int>* session_impl::lookup_as(int as) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
std::map<int, int>::iterator i = m_as_peak.lower_bound(as); |
|
|
|
if (i == m_as_peak.end() || i->first != as) |
|
{ |
|
// we don't have any data for this AS, insert a new entry |
|
i = m_as_peak.insert(i, std::pair<int, int>(as, 0)); |
|
} |
|
return &(*i); |
|
} |
|
|
|
void session_impl::load_asnum_db(std::string file) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_asnum_db) GeoIP_delete(m_asnum_db); |
|
m_asnum_db = GeoIP_open(file.c_str(), GEOIP_STANDARD); |
|
// return m_asnum_db; |
|
} |
|
|
|
#if TORRENT_USE_WSTRING |
|
void session_impl::load_asnum_dbw(std::wstring file) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_asnum_db) GeoIP_delete(m_asnum_db); |
|
std::string utf8; |
|
wchar_utf8(file, utf8); |
|
m_asnum_db = GeoIP_open(utf8.c_str(), GEOIP_STANDARD); |
|
// return m_asnum_db; |
|
} |
|
|
|
void session_impl::load_country_dbw(std::wstring file) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_country_db) GeoIP_delete(m_country_db); |
|
std::string utf8; |
|
wchar_utf8(file, utf8); |
|
m_country_db = GeoIP_open(utf8.c_str(), GEOIP_STANDARD); |
|
// return m_country_db; |
|
} |
|
#endif // TORRENT_USE_WSTRING |
|
|
|
void session_impl::load_country_db(std::string file) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_country_db) GeoIP_delete(m_country_db); |
|
m_country_db = GeoIP_open(file.c_str(), GEOIP_STANDARD); |
|
// return m_country_db; |
|
} |
|
|
|
#endif // TORRENT_DISABLE_GEO_IP |
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS |
|
|
|
typedef boost::function<boost::shared_ptr<torrent_plugin>(torrent*, void*)> ext_function_t; |
|
|
|
struct session_plugin_wrapper : plugin |
|
{ |
|
session_plugin_wrapper(ext_function_t const& f) : m_f(f) {} |
|
|
|
virtual boost::shared_ptr<torrent_plugin> new_torrent(torrent* t, void* user) |
|
{ return m_f(t, user); } |
|
ext_function_t m_f; |
|
}; |
|
|
|
void session_impl::add_extension(ext_function_t ext) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
TORRENT_ASSERT_VAL(ext, ext); |
|
|
|
boost::shared_ptr<plugin> p(new session_plugin_wrapper(ext)); |
|
|
|
m_ses_extensions.push_back(p); |
|
} |
|
|
|
void session_impl::add_ses_extension(boost::shared_ptr<plugin> ext) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
TORRENT_ASSERT_VAL(ext, ext); |
|
|
|
m_ses_extensions.push_back(ext); |
|
m_alerts.add_extension(ext); |
|
ext->added(shared_from_this()); |
|
} |
|
#endif |
|
|
|
feed_handle session_impl::add_feed(feed_settings const& sett) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
// look for duplicates. If we already have a feed with this |
|
// URL, return a handle to the existing one |
|
for (std::vector<boost::shared_ptr<feed> >::const_iterator i |
|
= m_feeds.begin(), end(m_feeds.end()); i != end; ++i) |
|
{ |
|
if (sett.url != (*i)->m_settings.url) continue; |
|
return feed_handle(*i); |
|
} |
|
|
|
boost::shared_ptr<feed> f(new_feed(*this, sett)); |
|
m_feeds.push_back(f); |
|
update_rss_feeds(); |
|
return feed_handle(f); |
|
} |
|
|
|
void session_impl::remove_feed(feed_handle h) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
boost::shared_ptr<feed> f = h.m_feed_ptr.lock(); |
|
if (!f) return; |
|
|
|
std::vector<boost::shared_ptr<feed> >::iterator i |
|
= std::find(m_feeds.begin(), m_feeds.end(), f); |
|
|
|
if (i == m_feeds.end()) return; |
|
|
|
m_feeds.erase(i); |
|
} |
|
|
|
void session_impl::get_feeds(std::vector<feed_handle>* ret) const |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
ret->clear(); |
|
ret->reserve(m_feeds.size()); |
|
for (std::vector<boost::shared_ptr<feed> >::const_iterator i = m_feeds.begin() |
|
, end(m_feeds.end()); i != end; ++i) |
|
ret->push_back(feed_handle(*i)); |
|
} |
|
|
|
void session_impl::pause() |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_paused) return; |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
session_log(" *** session paused ***"); |
|
#endif |
|
m_paused = true; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
torrent& t = *i->second; |
|
t.do_pause(); |
|
} |
|
} |
|
|
|
void session_impl::resume() |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (!m_paused) return; |
|
m_paused = false; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
torrent& t = *i->second; |
|
t.do_resume(); |
|
if (t.should_check_files()) t.queue_torrent_check(); |
|
} |
|
} |
|
|
|
void session_impl::abort() |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_abort) return; |
|
#if defined TORRENT_LOGGING |
|
session_log(" *** ABORT CALLED ***"); |
|
#endif |
|
// abort the main thread |
|
m_abort = true; |
|
error_code ec; |
|
#if TORRENT_USE_I2P |
|
m_i2p_conn.close(ec); |
|
#endif |
|
m_queued_for_checking.clear(); |
|
stop_lsd(); |
|
stop_upnp(); |
|
stop_natpmp(); |
|
#ifndef TORRENT_DISABLE_DHT |
|
stop_dht(); |
|
m_dht_announce_timer.cancel(ec); |
|
#endif |
|
m_timer.cancel(ec); |
|
m_lsd_announce_timer.cancel(ec); |
|
|
|
// close the listen sockets |
|
for (std::list<listen_socket_t>::iterator i = m_listen_sockets.begin() |
|
, end(m_listen_sockets.end()); i != end; ++i) |
|
{ |
|
i->sock->close(ec); |
|
TORRENT_ASSERT(!ec); |
|
} |
|
m_listen_sockets.clear(); |
|
if (m_socks_listen_socket && m_socks_listen_socket->is_open()) |
|
m_socks_listen_socket->close(); |
|
m_socks_listen_socket.reset(); |
|
|
|
#if TORRENT_USE_I2P |
|
if (m_i2p_listen_socket && m_i2p_listen_socket->is_open()) |
|
m_i2p_listen_socket->close(); |
|
m_i2p_listen_socket.reset(); |
|
#endif |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" aborting all torrents (%d)", m_torrents.size()); |
|
#endif |
|
// abort all torrents |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
i->second->abort(); |
|
} |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" aborting all tracker requests"); |
|
#endif |
|
m_tracker_manager.abort_all_requests(); |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" sending event=stopped to trackers"); |
|
#endif |
|
for (torrent_map::iterator i = m_torrents.begin(); |
|
i != m_torrents.end(); ++i) |
|
{ |
|
torrent& t = *i->second; |
|
t.abort(); |
|
} |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" aborting all connections (%d)", m_connections.size()); |
|
#endif |
|
m_half_open.close(); |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" connection queue: %d", m_half_open.size()); |
|
#endif |
|
|
|
// abort all connections |
|
while (!m_connections.empty()) |
|
{ |
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS |
|
int conn = m_connections.size(); |
|
#endif |
|
(*m_connections.begin())->disconnect(errors::stopping_torrent); |
|
TORRENT_ASSERT_VAL(conn == int(m_connections.size()) + 1, conn); |
|
} |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" connection queue: %d", m_half_open.size()); |
|
#endif |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" shutting down connection queue"); |
|
#endif |
|
|
|
m_download_rate.close(); |
|
m_upload_rate.close(); |
|
|
|
// #error closing the udp socket here means that |
|
// the uTP connections cannot be closed gracefully |
|
m_udp_socket.close(); |
|
m_external_udp_port = 0; |
|
|
|
#ifndef TORRENT_DISABLE_GEO_IP |
|
if (m_asnum_db) GeoIP_delete(m_asnum_db); |
|
if (m_country_db) GeoIP_delete(m_country_db); |
|
m_asnum_db = 0; |
|
m_country_db = 0; |
|
#endif |
|
|
|
m_disk_thread.abort(); |
|
} |
|
|
|
void session_impl::set_port_filter(port_filter const& f) |
|
{ |
|
m_port_filter = f; |
|
// TODO: recalculate all connect candidates for all torrents |
|
} |
|
|
|
void session_impl::set_ip_filter(ip_filter const& f) |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
m_ip_filter = f; |
|
|
|
// Close connections whose endpoint is filtered |
|
// by the new ip-filter |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
i->second->ip_filter_updated(); |
|
} |
|
|
|
ip_filter const& session_impl::get_ip_filter() const |
|
{ |
|
return m_ip_filter; |
|
} |
|
|
|
void session_impl::update_disk_thread_settings() |
|
{ |
|
disk_io_job j; |
|
j.buffer = (char*)new session_settings(m_settings); |
|
j.action = disk_io_job::update_settings; |
|
m_disk_thread.add_job(j); |
|
} |
|
|
|
void session_impl::set_settings(session_settings const& s) |
|
{ |
|
INVARIANT_CHECK; |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
TORRENT_ASSERT_VAL(s.file_pool_size > 0, s.file_pool_size); |
|
|
|
// less than 5 seconds unchoke interval is insane |
|
TORRENT_ASSERT_VAL(s.unchoke_interval >= 5, s.unchoke_interval); |
|
|
|
// if disk io thread settings were changed |
|
// post a notification to that thread |
|
bool update_disk_io_thread = false; |
|
if (m_settings.cache_size != s.cache_size |
|
|| m_settings.cache_expiry != s.cache_expiry |
|
|| m_settings.optimize_hashing_for_speed != s.optimize_hashing_for_speed |
|
|| m_settings.file_checks_delay_per_block != s.file_checks_delay_per_block |
|
|| m_settings.disk_cache_algorithm != s.disk_cache_algorithm |
|
|| m_settings.read_cache_line_size != s.read_cache_line_size |
|
|| m_settings.write_cache_line_size != s.write_cache_line_size |
|
|| m_settings.coalesce_writes != s.coalesce_writes |
|
|| m_settings.coalesce_reads != s.coalesce_reads |
|
|| m_settings.max_queued_disk_bytes != s.max_queued_disk_bytes |
|
|| m_settings.max_queued_disk_bytes_low_watermark != s.max_queued_disk_bytes_low_watermark |
|
|| m_settings.disable_hash_checks != s.disable_hash_checks |
|
|| m_settings.explicit_read_cache != s.explicit_read_cache |
|
#ifndef TORRENT_DISABLE_MLOCK |
|
|| m_settings.lock_disk_cache != s.lock_disk_cache |
|
#endif |
|
|| m_settings.use_read_cache != s.use_read_cache |
|
|| m_settings.disk_io_write_mode != s.disk_io_write_mode |
|
|| m_settings.disk_io_read_mode != s.disk_io_read_mode |
|
|| m_settings.allow_reordered_disk_operations != s.allow_reordered_disk_operations |
|
|| m_settings.file_pool_size != s.file_pool_size |
|
|| m_settings.volatile_read_cache != s.volatile_read_cache |
|
|| m_settings.no_atime_storage!= s.no_atime_storage |
|
|| m_settings.ignore_resume_timestamps != s.ignore_resume_timestamps |
|
|| m_settings.no_recheck_incomplete_resume != s.no_recheck_incomplete_resume |
|
|| m_settings.low_prio_disk != s.low_prio_disk |
|
|| m_settings.lock_files != s.lock_files |
|
|| m_settings.use_disk_cache_pool != s.use_disk_cache_pool) |
|
update_disk_io_thread = true; |
|
|
|
bool connections_limit_changed = m_settings.connections_limit != s.connections_limit; |
|
bool unchoke_limit_changed = m_settings.unchoke_slots_limit != s.unchoke_slots_limit; |
|
|
|
#ifndef TORRENT_NO_DEPRECATE |
|
// support deprecated choker settings |
|
if (s.choking_algorithm == session_settings::rate_based_choker) |
|
{ |
|
if (s.auto_upload_slots && !s.auto_upload_slots_rate_based) |
|
m_settings.choking_algorithm = session_settings::auto_expand_choker; |
|
else if (!s.auto_upload_slots) |
|
m_settings.choking_algorithm = session_settings::fixed_slots_choker; |
|
} |
|
#endif |
|
|
|
// safety check |
|
if (m_settings.volatile_read_cache |
|
&& (m_settings.suggest_mode == session_settings::suggest_read_cache |
|
|| m_settings.explicit_read_cache)) |
|
{ |
|
// If you hit this assert, you're trying to set your cache to be |
|
// volatile and to suggest pieces out of it (or to make the cache |
|
// explicit) at the same time this is a bad configuration, don't do it |
|
TORRENT_ASSERT(false); |
|
m_settings.volatile_read_cache = false; |
|
} |
|
|
|
if (m_settings.choking_algorithm != s.choking_algorithm) |
|
{ |
|
// trigger recalculation of the unchoked peers |
|
m_unchoke_time_scaler = 0; |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_settings.dht_announce_interval != s.dht_announce_interval) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_dht_announce"); |
|
#endif |
|
error_code ec; |
|
int delay = (std::max)(s.dht_announce_interval |
|
/ (std::max)(int(m_torrents.size()), 1), 1); |
|
m_dht_announce_timer.expires_from_now(seconds(delay), ec); |
|
m_dht_announce_timer.async_wait( |
|
boost::bind(&session_impl::on_dht_announce, this, _1)); |
|
} |
|
#endif |
|
|
|
if (m_settings.local_service_announce_interval != s.local_service_announce_interval) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_lsd_announce"); |
|
#endif |
|
error_code ec; |
|
int delay = (std::max)(s.local_service_announce_interval |
|
/ (std::max)(int(m_torrents.size()), 1), 1); |
|
m_lsd_announce_timer.expires_from_now(seconds(delay), ec); |
|
m_lsd_announce_timer.async_wait( |
|
boost::bind(&session_impl::on_lsd_announce, this, _1)); |
|
} |
|
|
|
// if queuing settings were changed, recalculate |
|
// queued torrents sooner |
|
if ((m_settings.active_downloads != s.active_downloads |
|
|| m_settings.active_seeds != s.active_seeds |
|
|| m_settings.active_limit != s.active_limit)) |
|
m_auto_manage_time_scaler = 2; |
|
|
|
if (m_settings.report_web_seed_downloads != s.report_web_seed_downloads) |
|
{ |
|
// if this flag changed, update all web seed connections |
|
for (connection_map::iterator i = m_connections.begin() |
|
, end(m_connections.end()); i != end; ++i) |
|
{ |
|
int type = (*i)->type(); |
|
if (type == peer_connection::url_seed_connection |
|
|| type == peer_connection::http_seed_connection) |
|
(*i)->ignore_stats(!s.report_web_seed_downloads); |
|
} |
|
} |
|
|
|
if (m_settings.alert_queue_size != s.alert_queue_size) |
|
m_alerts.set_alert_queue_size_limit(s.alert_queue_size); |
|
|
|
if (m_settings.dht_upload_rate_limit != s.dht_upload_rate_limit) |
|
m_udp_socket.set_rate_limit(s.dht_upload_rate_limit); |
|
|
|
if (m_settings.peer_tos != s.peer_tos) |
|
{ |
|
error_code ec; |
|
m_udp_socket.set_option(type_of_service(s.peer_tos), ec); |
|
#if defined TORRENT_VERBOSE_LOGGING |
|
(*m_logger) << ">>> SET_TOS[ udp_socket tos: " << s.peer_tos << " e: " << ec.message() << " ]\n"; |
|
#endif |
|
} |
|
|
|
m_settings = s; |
|
|
|
if (m_settings.cache_buffer_chunk_size <= 0) |
|
m_settings.cache_buffer_chunk_size = 1; |
|
|
|
update_rate_settings(); |
|
|
|
if (connections_limit_changed) update_connections_limit(); |
|
if (unchoke_limit_changed) update_unchoke_limit(); |
|
|
|
bool anonymous_mode = (m_settings.anonymous_mode != s.anonymous_mode && s.anonymous_mode); |
|
if (anonymous_mode) |
|
{ |
|
m_settings.user_agent.clear(); |
|
url_random((char*)&m_peer_id[0], (char*)&m_peer_id[0] + 20); |
|
} |
|
|
|
bool force_proxy = (m_settings.force_proxy != s.force_proxy && s.force_proxy); |
|
|
|
m_udp_socket.set_force_proxy(s.force_proxy); |
|
|
|
// in force_proxy mode, we don't want to accept any incoming |
|
// connections, except through a proxy. |
|
if (force_proxy) |
|
{ |
|
stop_lsd(); |
|
stop_upnp(); |
|
stop_natpmp(); |
|
#ifndef TORRENT_DISABLE_DHT |
|
stop_dht(); |
|
#endif |
|
// close the listen sockets |
|
error_code ec; |
|
for (std::list<listen_socket_t>::iterator i = m_listen_sockets.begin() |
|
, end(m_listen_sockets.end()); i != end; ++i) |
|
i->sock->close(ec); |
|
m_listen_sockets.clear(); |
|
} |
|
if (m_settings.connection_speed < 0) m_settings.connection_speed = 200; |
|
|
|
if (update_disk_io_thread) |
|
update_disk_thread_settings(); |
|
|
|
if (m_settings.num_optimistic_unchoke_slots >= m_allowed_upload_slots / 2) |
|
{ |
|
if (m_alerts.should_post<performance_alert>()) |
|
m_alerts.post_alert(performance_alert(torrent_handle() |
|
, performance_alert::too_many_optimistic_unchoke_slots)); |
|
} |
|
|
|
if (s.choking_algorithm == session_settings::fixed_slots_choker) |
|
m_allowed_upload_slots = m_settings.unchoke_slots_limit; |
|
else if (s.choking_algorithm == session_settings::auto_expand_choker |
|
&& m_allowed_upload_slots < m_settings.unchoke_slots_limit) |
|
m_allowed_upload_slots = m_settings.unchoke_slots_limit; |
|
if (m_allowed_upload_slots < 0) |
|
m_allowed_upload_slots = (std::numeric_limits<int>::max)(); |
|
|
|
// replace all occurances of '\n' with ' '. |
|
std::string::iterator i = m_settings.user_agent.begin(); |
|
while ((i = std::find(i, m_settings.user_agent.end(), '\n')) |
|
!= m_settings.user_agent.end()) |
|
*i = ' '; |
|
} |
|
|
|
tcp::endpoint session_impl::get_ipv6_interface() const |
|
{ |
|
return m_ipv6_interface; |
|
} |
|
|
|
tcp::endpoint session_impl::get_ipv4_interface() const |
|
{ |
|
return m_ipv4_interface; |
|
} |
|
|
|
void session_impl::setup_listener(listen_socket_t* s, tcp::endpoint ep |
|
, int& retries, bool v6_only, int flags, error_code& ec) |
|
{ |
|
int last_op = 0; |
|
s->sock.reset(new socket_acceptor(m_io_service)); |
|
s->sock->open(ep.protocol(), ec); |
|
last_op = listen_failed_alert::open; |
|
if (ec) |
|
{ |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(ep, last_op, ec)); |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("failed to open socket: %s: %s" |
|
, print_endpoint(ep).c_str(), ec.message().c_str()); |
|
#endif |
|
return; |
|
} |
|
|
|
error_code err; // ignore errors here |
|
s->sock->set_option(socket_acceptor::reuse_address(true), err); |
|
|
|
#if TORRENT_USE_IPV6 |
|
if (ep.protocol() == tcp::v6()) |
|
{ |
|
error_code err; // ignore errors here |
|
#ifdef IPV6_V6ONLY |
|
s->sock->set_option(v6only(v6_only), err); |
|
#endif |
|
#ifdef TORRENT_WINDOWS |
|
|
|
#ifndef PROTECTION_LEVEL_UNRESTRICTED |
|
#define PROTECTION_LEVEL_UNRESTRICTED 10 |
|
#endif |
|
// enable Teredo on windows |
|
s->sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err); |
|
#endif |
|
} |
|
#endif |
|
s->sock->bind(ep, ec); |
|
while (ec && retries > 0) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("failed to bind to interface \"%s\": %s" |
|
, print_endpoint(ep).c_str(), ec.message().c_str()); |
|
#endif |
|
ec.clear(); |
|
TORRENT_ASSERT_VAL(!ec, ec); |
|
--retries; |
|
ep.port(ep.port() + 1); |
|
s->sock->bind(ep, ec); |
|
last_op = listen_failed_alert::bind; |
|
} |
|
if (ec && !(flags & session::listen_no_system_port)) |
|
{ |
|
// instead of giving up, trying |
|
// let the OS pick a port |
|
ep.port(0); |
|
ec = error_code(); |
|
s->sock->bind(ep, ec); |
|
last_op = listen_failed_alert::bind; |
|
} |
|
if (ec) |
|
{ |
|
// not even that worked, give up |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(ep, last_op, ec)); |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("cannot bind to interface \"%s\": %s" |
|
, print_endpoint(ep).c_str(), ec.message().c_str()); |
|
#endif |
|
return; |
|
} |
|
s->external_port = s->sock->local_endpoint(ec).port(); |
|
last_op = listen_failed_alert::get_peer_name; |
|
if (!ec) |
|
{ |
|
s->sock->listen(m_settings.listen_queue_size, ec); |
|
last_op = listen_failed_alert::listen; |
|
} |
|
if (ec) |
|
{ |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(ep, last_op, ec)); |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("cannot listen on interface \"%s\": %s" |
|
, print_endpoint(ep).c_str(), ec.message().c_str()); |
|
#endif |
|
return; |
|
} |
|
|
|
// if we asked the system to listen on port 0, which |
|
// socket did it end up choosing? |
|
if (ep.port() == 0) |
|
{ |
|
ep.port(s->sock->local_endpoint(ec).port()); |
|
last_op = listen_failed_alert::get_peer_name; |
|
if (ec) |
|
{ |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(ep, last_op, ec)); |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
char msg[200]; |
|
snprintf(msg, 200, "failed to get peer name \"%s\": %s" |
|
, print_endpoint(ep).c_str(), ec.message().c_str()); |
|
(*m_logger) << time_now_string() << msg << "\n"; |
|
#endif |
|
} |
|
} |
|
|
|
if (m_alerts.should_post<listen_succeeded_alert>()) |
|
m_alerts.post_alert(listen_succeeded_alert(ep)); |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log(" listening on: %s external port: %d" |
|
, print_endpoint(ep).c_str(), s->external_port); |
|
#endif |
|
} |
|
|
|
void session_impl::open_listen_port(int flags, error_code& ec) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
TORRENT_ASSERT(!m_abort); |
|
retry: |
|
|
|
// close the open listen sockets |
|
// close the listen sockets |
|
for (std::list<listen_socket_t>::iterator i = m_listen_sockets.begin() |
|
, end(m_listen_sockets.end()); i != end; ++i) |
|
i->sock->close(ec); |
|
m_listen_sockets.clear(); |
|
m_incoming_connection = false; |
|
ec.clear(); |
|
|
|
if (m_abort) return; |
|
|
|
m_ipv6_interface = tcp::endpoint(); |
|
m_ipv4_interface = tcp::endpoint(); |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
tcp::endpoint ssl_interface = m_listen_interface; |
|
ssl_interface.port(m_settings.ssl_listen); |
|
#endif |
|
|
|
if (is_any(m_listen_interface.address())) |
|
{ |
|
// this means we should open two listen sockets |
|
// one for IPv4 and one for IPv6 |
|
|
|
listen_socket_t s; |
|
setup_listener(&s, tcp::endpoint(address_v4::any(), m_listen_interface.port()) |
|
, m_listen_port_retries, false, flags, ec); |
|
|
|
if (s.sock) |
|
{ |
|
// update the listen_interface member with the |
|
// actual port we ended up listening on, so that the other |
|
// sockets can be bound to the same one |
|
m_listen_interface.port(s.external_port); |
|
|
|
TORRENT_ASSERT(!m_abort); |
|
m_listen_sockets.push_back(s); |
|
} |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
if (m_settings.ssl_listen) |
|
{ |
|
listen_socket_t s; |
|
s.ssl = true; |
|
int retries = 10; |
|
setup_listener(&s, ssl_interface, retries, false, flags, ec); |
|
|
|
if (s.sock) |
|
{ |
|
TORRENT_ASSERT(!m_abort); |
|
m_listen_sockets.push_back(s); |
|
} |
|
} |
|
#endif |
|
|
|
#if TORRENT_USE_IPV6 |
|
// only try to open the IPv6 port if IPv6 is installed |
|
if (supports_ipv6()) |
|
{ |
|
setup_listener(&s, tcp::endpoint(address_v6::any(), m_listen_interface.port()) |
|
, m_listen_port_retries, true, flags, ec); |
|
|
|
if (s.sock) |
|
{ |
|
TORRENT_ASSERT(!m_abort); |
|
m_listen_sockets.push_back(s); |
|
} |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
if (m_settings.ssl_listen) |
|
{ |
|
listen_socket_t s; |
|
s.ssl = true; |
|
int retries = 10; |
|
setup_listener(&s, tcp::endpoint(address_v6::any(), ssl_interface.port()) |
|
, retries, false, flags, ec); |
|
|
|
if (s.sock) |
|
{ |
|
TORRENT_ASSERT(!m_abort); |
|
m_listen_sockets.push_back(s); |
|
} |
|
} |
|
#endif // TORRENT_USE_OPENSSL |
|
} |
|
#endif // TORRENT_USE_IPV6 |
|
|
|
// set our main IPv4 and IPv6 interfaces |
|
// used to send to the tracker |
|
std::vector<ip_interface> ifs = enum_net_interfaces(m_io_service, ec); |
|
for (std::vector<ip_interface>::const_iterator i = ifs.begin() |
|
, end(ifs.end()); i != end; ++i) |
|
{ |
|
address const& addr = i->interface_address; |
|
if (addr.is_v6() && !is_local(addr) && !is_loopback(addr)) |
|
m_ipv6_interface = tcp::endpoint(addr, m_listen_interface.port()); |
|
else if (addr.is_v4() && !is_local(addr) && !is_loopback(addr)) |
|
m_ipv4_interface = tcp::endpoint(addr, m_listen_interface.port()); |
|
} |
|
} |
|
else |
|
{ |
|
// we should only open a single listen socket, that |
|
// binds to the given interface |
|
|
|
listen_socket_t s; |
|
setup_listener(&s, m_listen_interface, m_listen_port_retries, false, flags, ec); |
|
|
|
if (s.sock) |
|
{ |
|
TORRENT_ASSERT(!m_abort); |
|
m_listen_sockets.push_back(s); |
|
|
|
if (m_listen_interface.address().is_v6()) |
|
m_ipv6_interface = m_listen_interface; |
|
else |
|
m_ipv4_interface = m_listen_interface; |
|
} |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
if (m_settings.ssl_listen) |
|
{ |
|
listen_socket_t s; |
|
s.ssl = true; |
|
int retries = 10; |
|
setup_listener(&s, ssl_interface, retries, false, flags, ec); |
|
|
|
if (s.sock) |
|
{ |
|
TORRENT_ASSERT(!m_abort); |
|
m_listen_sockets.push_back(s); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
m_udp_socket.bind(udp::endpoint(m_listen_interface.address(), m_listen_interface.port()), ec); |
|
if (ec) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("cannot bind to UDP interface \"%s\": %s" |
|
, print_endpoint(m_listen_interface).c_str(), ec.message().c_str()); |
|
#endif |
|
if (m_listen_port_retries > 0) |
|
{ |
|
m_listen_interface.port(m_listen_interface.port() + 1); |
|
--m_listen_port_retries; |
|
goto retry; |
|
} |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(m_listen_interface |
|
, listen_failed_alert::bind, ec)); |
|
} |
|
else |
|
{ |
|
m_external_udp_port = m_udp_socket.local_port(); |
|
maybe_update_udp_mapping(0, m_listen_interface.port(), m_listen_interface.port()); |
|
maybe_update_udp_mapping(1, m_listen_interface.port(), m_listen_interface.port()); |
|
} |
|
|
|
m_udp_socket.set_option(type_of_service(m_settings.peer_tos), ec); |
|
#if defined TORRENT_VERBOSE_LOGGING |
|
(*m_logger) << ">>> SET_TOS[ udp_socket tos: " << m_settings.peer_tos << " e: " << ec.message() << " ]\n"; |
|
#endif |
|
ec.clear(); |
|
|
|
// initiate accepting on the listen sockets |
|
for (std::list<listen_socket_t>::iterator i = m_listen_sockets.begin() |
|
, end(m_listen_sockets.end()); i != end; ++i) |
|
async_accept(i->sock, i->ssl); |
|
|
|
open_new_incoming_socks_connection(); |
|
#if TORRENT_USE_I2P |
|
open_new_incoming_i2p_connection(); |
|
#endif |
|
|
|
if (!m_listen_sockets.empty()) |
|
{ |
|
tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec); |
|
if (!ec) remap_tcp_ports(3, local.port(), ssl_listen_port()); |
|
} |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
m_logger = create_log("main_session", listen_port(), false); |
|
#endif |
|
} |
|
|
|
void session_impl::remap_tcp_ports(boost::uint32_t mask, int tcp_port, int ssl_port) |
|
{ |
|
if ((mask & 1) && m_natpmp.get()) |
|
{ |
|
if (m_tcp_mapping[0] != -1) m_natpmp->delete_mapping(m_tcp_mapping[0]); |
|
m_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp, tcp_port, tcp_port); |
|
if (m_twister_tcp_mapping[0] != -1) m_natpmp->delete_mapping(m_twister_tcp_mapping[0]); |
|
m_twister_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp, |
|
tcp_port-LIBTORRENT_PORT_OFFSET, tcp_port-LIBTORRENT_PORT_OFFSET); |
|
#ifdef TORRENT_USE_OPENSSL |
|
if (m_ssl_mapping[0] != -1) m_natpmp->delete_mapping(m_ssl_mapping[0]); |
|
m_ssl_mapping[0] = m_natpmp->add_mapping(natpmp::tcp, ssl_port, ssl_port); |
|
#endif |
|
} |
|
if ((mask & 2) && m_upnp.get()) |
|
{ |
|
if (m_tcp_mapping[1] != -1) m_upnp->delete_mapping(m_tcp_mapping[1]); |
|
m_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp, tcp_port, tcp_port); |
|
if (m_twister_tcp_mapping[1] != -1) m_upnp->delete_mapping(m_twister_tcp_mapping[1]); |
|
m_twister_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp, |
|
tcp_port-LIBTORRENT_PORT_OFFSET, tcp_port-LIBTORRENT_PORT_OFFSET); |
|
#ifdef TORRENT_USE_OPENSSL |
|
if (m_ssl_mapping[1] != -1) m_upnp->delete_mapping(m_ssl_mapping[1]); |
|
m_ssl_mapping[1] = m_upnp->add_mapping(upnp::tcp, ssl_port, ssl_port); |
|
#endif |
|
} |
|
} |
|
|
|
void session_impl::open_new_incoming_socks_connection() |
|
{ |
|
if (m_proxy.type != proxy_settings::socks5 |
|
&& m_proxy.type != proxy_settings::socks5_pw |
|
&& m_proxy.type != proxy_settings::socks4) |
|
return; |
|
|
|
if (m_socks_listen_socket) return; |
|
|
|
m_socks_listen_socket = boost::shared_ptr<socket_type>(new socket_type(m_io_service)); |
|
bool ret = instantiate_connection(m_io_service, m_proxy |
|
, *m_socks_listen_socket); |
|
TORRENT_ASSERT_VAL(ret, ret); |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_socks_accept"); |
|
#endif |
|
socks5_stream& s = *m_socks_listen_socket->get<socks5_stream>(); |
|
s.set_command(2); // 2 means BIND (as opposed to CONNECT) |
|
m_socks_listen_port = m_listen_interface.port(); |
|
if (m_socks_listen_port == 0) m_socks_listen_port = 2000 + random() % 60000; |
|
s.async_connect(tcp::endpoint(address_v4::any(), m_socks_listen_port) |
|
, boost::bind(&session_impl::on_socks_accept, this, m_socks_listen_socket, _1)); |
|
} |
|
|
|
#if TORRENT_USE_I2P |
|
void session_impl::on_i2p_open(error_code const& ec) |
|
{ |
|
open_new_incoming_i2p_connection(); |
|
} |
|
|
|
void session_impl::open_new_incoming_i2p_connection() |
|
{ |
|
if (!m_i2p_conn.is_open()) return; |
|
|
|
if (m_i2p_listen_socket) return; |
|
|
|
m_i2p_listen_socket = boost::shared_ptr<socket_type>(new socket_type(m_io_service)); |
|
bool ret = instantiate_connection(m_io_service, m_i2p_conn.proxy() |
|
, *m_i2p_listen_socket); |
|
TORRENT_ASSERT_VAL(ret, ret); |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_i2p_accept"); |
|
#endif |
|
i2p_stream& s = *m_i2p_listen_socket->get<i2p_stream>(); |
|
s.set_command(i2p_stream::cmd_accept); |
|
s.set_session_id(m_i2p_conn.session_id()); |
|
s.async_connect(tcp::endpoint(address_v4::any(), m_listen_interface.port()) |
|
, boost::bind(&session_impl::on_i2p_accept, this, m_i2p_listen_socket, _1)); |
|
} |
|
|
|
void session_impl::on_i2p_accept(boost::shared_ptr<socket_type> const& s |
|
, error_code const& e) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_i2p_accept"); |
|
#endif |
|
m_i2p_listen_socket.reset(); |
|
if (e == asio::error::operation_aborted) return; |
|
if (e) |
|
{ |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(tcp::endpoint( |
|
address_v4::any(), m_listen_interface.port()), listen_failed_alert::accept, e)); |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("cannot bind to port %d: %s" |
|
, m_listen_interface.port(), e.message().c_str()); |
|
#endif |
|
return; |
|
} |
|
open_new_incoming_i2p_connection(); |
|
incoming_connection(s); |
|
} |
|
#endif |
|
|
|
bool session_impl::incoming_packet(error_code const& ec |
|
, udp::endpoint const& ep, char const* buf, int size) |
|
{ |
|
#ifdef TORRENT_STATS |
|
++m_num_messages[on_udp_counter]; |
|
#endif |
|
|
|
if (ec) |
|
{ |
|
// don't bubble up operation aborted errors to the user |
|
if (ec != asio::error::operation_aborted |
|
&& m_alerts.should_post<udp_error_alert>()) |
|
m_alerts.post_alert(udp_error_alert(ep, ec)); |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("UDP socket error: (%d) %s", ec.value(), ec.message().c_str()); |
|
#endif |
|
} |
|
return false; |
|
} |
|
|
|
void session_impl::async_accept(boost::shared_ptr<socket_acceptor> const& listener, bool ssl) |
|
{ |
|
TORRENT_ASSERT(!m_abort); |
|
shared_ptr<socket_type> c(new socket_type(m_io_service)); |
|
stream_socket* str = 0; |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
if (ssl) |
|
{ |
|
// accept connections initializing the SSL connection to |
|
// use the generic m_ssl_ctx context. However, since it has |
|
// the servername callback set on it, we will switch away from |
|
// this context into a specific torrent once we start handshaking |
|
c->instantiate<ssl_stream<stream_socket> >(m_io_service, &m_ssl_ctx); |
|
str = &c->get<ssl_stream<stream_socket> >()->next_layer(); |
|
} |
|
else |
|
#endif |
|
{ |
|
c->instantiate<stream_socket>(m_io_service); |
|
str = c->get<stream_socket>(); |
|
} |
|
|
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_accept_connection"); |
|
#endif |
|
listener->async_accept(*str |
|
, boost::bind(&session_impl::on_accept_connection, this, c |
|
, boost::weak_ptr<socket_acceptor>(listener), _1, ssl)); |
|
} |
|
|
|
void session_impl::on_accept_connection(shared_ptr<socket_type> const& s |
|
, weak_ptr<socket_acceptor> listen_socket, error_code const& e, bool ssl) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_accept_connection"); |
|
#endif |
|
#ifdef TORRENT_STATS |
|
++m_num_messages[on_accept_counter]; |
|
#endif |
|
TORRENT_ASSERT(is_network_thread()); |
|
boost::shared_ptr<socket_acceptor> listener = listen_socket.lock(); |
|
if (!listener) return; |
|
|
|
if (e == asio::error::operation_aborted) return; |
|
|
|
if (m_abort) return; |
|
|
|
error_code ec; |
|
if (e) |
|
{ |
|
tcp::endpoint ep = listener->local_endpoint(ec); |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log("error accepting connection on '%s': %s" |
|
, print_endpoint(ep).c_str(), e.message().c_str()); |
|
#endif |
|
#ifdef TORRENT_WINDOWS |
|
// Windows sometimes generates this error. It seems to be |
|
// non-fatal and we have to do another async_accept. |
|
if (e.value() == ERROR_SEM_TIMEOUT) |
|
{ |
|
async_accept(listener, ssl); |
|
return; |
|
} |
|
#endif |
|
#ifdef TORRENT_BSD |
|
// Leopard sometimes generates an "invalid argument" error. It seems to be |
|
// non-fatal and we have to do another async_accept. |
|
if (e.value() == EINVAL) |
|
{ |
|
async_accept(listener, ssl); |
|
return; |
|
} |
|
#endif |
|
if (e == boost::system::errc::too_many_files_open) |
|
{ |
|
// if we failed to accept an incoming connection |
|
// because we have too many files open, try again |
|
// and lower the number of file descriptors used |
|
// elsewere. |
|
if (m_settings.connections_limit > 10) |
|
{ |
|
// now, disconnect a random peer |
|
torrent_map::iterator i = std::max_element(m_torrents.begin() |
|
, m_torrents.end(), boost::bind(&torrent::num_peers |
|
, boost::bind(&torrent_map::value_type::second, _1))); |
|
|
|
if (m_alerts.should_post<performance_alert>()) |
|
m_alerts.post_alert(performance_alert( |
|
torrent_handle(), performance_alert::too_few_file_descriptors)); |
|
|
|
if (i != m_torrents.end()) |
|
{ |
|
i->second->disconnect_peers(1, e); |
|
} |
|
|
|
m_settings.connections_limit = m_connections.size(); |
|
} |
|
// try again, but still alert the user of the problem |
|
async_accept(listener, ssl); |
|
} |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(ep, listen_failed_alert::accept, e)); |
|
return; |
|
} |
|
async_accept(listener, ssl); |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
if (ssl) |
|
{ |
|
// for SSL connections, incoming_connection() is called |
|
// after the handshake is done |
|
s->get<ssl_stream<stream_socket> >()->async_accept_handshake( |
|
boost::bind(&session_impl::ssl_handshake, this, _1, s)); |
|
} |
|
else |
|
#endif |
|
{ |
|
incoming_connection(s); |
|
} |
|
} |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
|
|
// to test SSL connections, one can use this openssl command template: |
|
// |
|
// openssl s_client -cert <client-cert>.pem -key <client-private-key>.pem \ |
|
// -CAfile <torrent-cert>.pem -debug -connect 127.0.0.1:4433 -tls1 \ |
|
// -servername <hex-encoded-info-hash> |
|
|
|
void session_impl::ssl_handshake(error_code const& ec, boost::shared_ptr<socket_type> s) |
|
{ |
|
error_code e; |
|
tcp::endpoint endp = s->remote_endpoint(e); |
|
if (e) return; |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" *** peer SSL handshake done [ ip: %s ec: %s socket: %s ]" |
|
, print_endpoint(endp).c_str(), ec.message().c_str(), s->type_name()); |
|
#endif |
|
|
|
if (ec) |
|
{ |
|
if (m_alerts.should_post<peer_error_alert>()) |
|
{ |
|
m_alerts.post_alert(peer_error_alert(torrent_handle(), endp |
|
, peer_id(), ec)); |
|
} |
|
return; |
|
} |
|
|
|
incoming_connection(s); |
|
} |
|
|
|
#endif // TORRENT_USE_OPENSSL |
|
|
|
void session_impl::incoming_connection(boost::shared_ptr<socket_type> const& s) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
#ifdef TORRENT_USE_OPENSSL |
|
// add the current time to the PRNG, to add more unpredictability |
|
boost::uint64_t now = total_microseconds(time_now_hires() - min_time()); |
|
// assume 12 bits of entropy (i.e. about 8 milliseconds) |
|
RAND_add(&now, 8, 1.5); |
|
#endif |
|
|
|
if (m_paused) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
//session_log(" <== INCOMING CONNECTION [ ignored, paused ]"); |
|
#endif |
|
return; |
|
} |
|
|
|
error_code ec; |
|
// we got a connection request! |
|
tcp::endpoint endp = s->remote_endpoint(ec); |
|
|
|
if (ec) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log("%s <== INCOMING CONNECTION FAILED, could " |
|
"not retrieve remote endpoint " |
|
, print_endpoint(endp).c_str(), ec.message().c_str()); |
|
#endif |
|
return; |
|
} |
|
|
|
TORRENT_ASSERT(endp.address() != address_v4::any()); |
|
|
|
/* |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" <== INCOMING CONNECTION %s type: %s" |
|
, print_endpoint(endp).c_str(), s->type_name()); |
|
#endif |
|
*/ |
|
|
|
if (m_alerts.should_post<incoming_connection_alert>()) |
|
{ |
|
m_alerts.post_alert(incoming_connection_alert(s->type(), endp)); |
|
} |
|
|
|
if (!m_settings.enable_incoming_utp |
|
&& s->get<utp_stream>()) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" rejected uTP connection"); |
|
#endif |
|
if (m_alerts.should_post<peer_blocked_alert>()) |
|
m_alerts.post_alert(peer_blocked_alert(torrent_handle(), endp.address())); |
|
return; |
|
} |
|
|
|
if (!m_settings.enable_incoming_tcp |
|
&& s->get<stream_socket>()) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" rejected TCP connection"); |
|
#endif |
|
if (m_alerts.should_post<peer_blocked_alert>()) |
|
m_alerts.post_alert(peer_blocked_alert(torrent_handle(), endp.address())); |
|
return; |
|
} |
|
|
|
// local addresses do not count, since it's likely |
|
// coming from our own client through local service discovery |
|
// and it does not reflect whether or not a router is open |
|
// for incoming connections or not. |
|
if (!is_local(endp.address())) |
|
m_incoming_connection = true; |
|
|
|
// this filter is ignored if a single torrent |
|
// is set to ignore the filter, since this peer might be |
|
// for that torrent |
|
if (m_non_filtered_torrents == 0 |
|
&& (m_ip_filter.access(endp.address()) & ip_filter::blocked)) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log("filtered blocked ip"); |
|
#endif |
|
if (m_alerts.should_post<peer_blocked_alert>()) |
|
m_alerts.post_alert(peer_blocked_alert(torrent_handle(), endp.address())); |
|
return; |
|
} |
|
|
|
// check if we have any active torrents |
|
// if we don't reject the connection |
|
if (m_torrents.empty()) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" There are no torrents, disconnect"); |
|
#endif |
|
return; |
|
} |
|
|
|
// don't allow more connections than the max setting |
|
bool reject = false; |
|
if (m_settings.ignore_limits_on_local_network && is_local(endp.address())) |
|
reject = m_settings.connections_limit < INT_MAX / 12 |
|
&& num_connections() >= m_settings.connections_limit * 12 / 10; |
|
else |
|
reject = num_connections() >= m_settings.connections_limit + m_settings.connections_slack; |
|
|
|
if (reject) |
|
{ |
|
if (m_alerts.should_post<peer_disconnected_alert>()) |
|
{ |
|
m_alerts.post_alert( |
|
peer_disconnected_alert(torrent_handle(), endp, peer_id() |
|
, error_code(errors::too_many_connections, get_libtorrent_category()))); |
|
} |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log("number of connections limit exceeded (conns: %d" |
|
", limit: %d slack: %d), connection rejected\n" |
|
, num_connections(), m_settings.connections_limit, m_settings.connections_slack); |
|
#endif |
|
return; |
|
} |
|
|
|
// if we don't have any active torrents, there's no |
|
// point in accepting this connection. If, however, |
|
// the setting to start up queued torrents when they |
|
// get an incoming connection is enabled, we cannot |
|
// perform this check. |
|
if (!m_settings.incoming_starts_queued_torrents) |
|
{ |
|
bool has_active_torrent = false; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
if (i->second->allows_peers()) |
|
{ |
|
has_active_torrent = true; |
|
break; |
|
} |
|
} |
|
if (!has_active_torrent) |
|
{ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" There are no _active_ torrents, disconnect"); |
|
#endif |
|
return; |
|
} |
|
} |
|
|
|
setup_socket_buffers(*s); |
|
|
|
boost::intrusive_ptr<peer_connection> c( |
|
new bt_peer_connection(*this, s, endp, 0)); |
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS |
|
c->m_in_constructor = false; |
|
#endif |
|
|
|
if (!c->is_disconnecting()) |
|
{ |
|
// in case we've exceeded the limit, let this peer know that |
|
// as soon as it's received the handshake, it needs to either |
|
// disconnect or pick another peer to disconnect |
|
if (num_connections() >= m_settings.connections_limit) |
|
c->peer_exceeds_limit(); |
|
|
|
m_connections.insert(c); |
|
c->start(); |
|
// update the next disk peer round-robin cursor |
|
if (m_next_disk_peer == m_connections.end()) m_next_disk_peer = m_connections.begin(); |
|
} |
|
} |
|
|
|
void session_impl::setup_socket_buffers(socket_type& s) |
|
{ |
|
error_code ec; |
|
if (m_settings.send_socket_buffer_size) |
|
{ |
|
stream_socket::send_buffer_size option( |
|
m_settings.send_socket_buffer_size); |
|
s.set_option(option, ec); |
|
} |
|
if (m_settings.recv_socket_buffer_size) |
|
{ |
|
stream_socket::receive_buffer_size option( |
|
m_settings.recv_socket_buffer_size); |
|
s.set_option(option, ec); |
|
} |
|
} |
|
|
|
void session_impl::on_socks_accept(boost::shared_ptr<socket_type> const& s |
|
, error_code const& e) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_socks_accept"); |
|
#endif |
|
m_socks_listen_socket.reset(); |
|
if (e == asio::error::operation_aborted) return; |
|
if (e) |
|
{ |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(tcp::endpoint( |
|
address_v4::any(), m_listen_interface.port()), listen_failed_alert::accept, e)); |
|
return; |
|
} |
|
open_new_incoming_socks_connection(); |
|
incoming_connection(s); |
|
} |
|
|
|
void session_impl::close_connection(peer_connection const* p |
|
, error_code const& ec) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
// too expensive |
|
// INVARIANT_CHECK; |
|
|
|
#ifdef TORRENT_DEBUG |
|
// for (aux::session_impl::torrent_map::const_iterator i = m_torrents.begin() |
|
// , end(m_torrents.end()); i != end; ++i) |
|
// TORRENT_ASSERT(!i->second->has_peer((peer_connection*)p)); |
|
#endif |
|
|
|
#if defined(TORRENT_LOGGING) |
|
/* |
|
session_log(" CLOSING CONNECTION %s : %s" |
|
, print_endpoint(p->remote()).c_str(), ec.message().c_str()); |
|
*/ |
|
#endif |
|
|
|
TORRENT_ASSERT(p->is_disconnecting()); |
|
|
|
if (!p->is_choked() && !p->ignore_unchoke_slots()) --m_num_unchoked; |
|
TORRENT_ASSERT(p->refcount() > 0); |
|
|
|
boost::intrusive_ptr<peer_connection> sp((peer_connection*)p); |
|
connection_map::iterator i = m_connections.find(sp); |
|
// make sure the next disk peer round-robin cursor stays valid |
|
if (m_next_disk_peer == i) ++m_next_disk_peer; |
|
if (i != m_connections.end()) m_connections.erase(i); |
|
if (m_next_disk_peer == m_connections.end()) m_next_disk_peer = m_connections.begin(); |
|
} |
|
|
|
// implements alert_dispatcher |
|
bool session_impl::post_alert(alert* a) |
|
{ |
|
if (!m_alerts.should_post(a)) return false; |
|
m_alerts.post_alert_ptr(a); |
|
return true; |
|
} |
|
|
|
void session_impl::set_peer_id(peer_id const& id) |
|
{ |
|
m_peer_id = id; |
|
} |
|
|
|
void session_impl::set_key(int key) |
|
{ |
|
m_key = key; |
|
} |
|
|
|
void session_impl::unchoke_peer(peer_connection& c) |
|
{ |
|
TORRENT_ASSERT(!c.ignore_unchoke_slots()); |
|
torrent* t = c.associated_torrent().lock().get(); |
|
TORRENT_ASSERT(t); |
|
if (t->unchoke_peer(c)) |
|
++m_num_unchoked; |
|
} |
|
|
|
void session_impl::choke_peer(peer_connection& c) |
|
{ |
|
TORRENT_ASSERT(!c.ignore_unchoke_slots()); |
|
torrent* t = c.associated_torrent().lock().get(); |
|
TORRENT_ASSERT(t); |
|
if (t->choke_peer(c)) |
|
--m_num_unchoked; |
|
} |
|
|
|
int session_impl::next_port() |
|
{ |
|
std::pair<int, int> const& out_ports = m_settings.outgoing_ports; |
|
if (m_next_port < out_ports.first || m_next_port > out_ports.second) |
|
m_next_port = out_ports.first; |
|
|
|
int port = m_next_port; |
|
++m_next_port; |
|
if (m_next_port > out_ports.second) m_next_port = out_ports.first; |
|
#if defined TORRENT_LOGGING |
|
session_log(" *** BINDING OUTGOING CONNECTION [ port: %d ]", port); |
|
#endif |
|
return port; |
|
} |
|
|
|
// this function is called from the disk-io thread |
|
// when the disk queue is low enough to post new |
|
// write jobs to it. It will go through all peer |
|
// connections that are blocked on the disk and |
|
// wake them up |
|
void session_impl::on_disk_queue() |
|
{ |
|
#ifdef TORRENT_STATS |
|
++m_num_messages[on_disk_queue_counter]; |
|
#endif |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
// just to play it safe |
|
if (m_next_disk_peer == m_connections.end()) m_next_disk_peer = m_connections.begin(); |
|
|
|
// never loop more times than there are connections |
|
// keep in mind that connections may disconnect |
|
// while we're looping, that's why this is a reliable |
|
// way of limiting it |
|
int limit = m_connections.size(); |
|
|
|
while (m_next_disk_peer != m_connections.end() && limit > 0 && can_write_to_disk()) |
|
{ |
|
--limit; |
|
peer_connection* p = m_next_disk_peer->get(); |
|
++m_next_disk_peer; |
|
if (m_next_disk_peer == m_connections.end()) m_next_disk_peer = m_connections.begin(); |
|
if ((p->m_channel_state[peer_connection::download_channel] |
|
& peer_info::bw_disk) == 0) continue; |
|
p->on_disk(); |
|
} |
|
|
|
} |
|
|
|
// used to cache the current time |
|
// every 100 ms. This is cheaper |
|
// than a system call and can be |
|
// used where more accurate time |
|
// is not necessary |
|
extern ptime g_current_time; |
|
|
|
initialize_timer::initialize_timer() |
|
{ |
|
g_current_time = time_now_hires(); |
|
} |
|
|
|
void session_impl::on_tick(error_code const& e) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_tick"); |
|
#endif |
|
#ifdef TORRENT_STATS |
|
++m_num_messages[on_tick_counter]; |
|
#endif |
|
|
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
ptime now = time_now_hires(); |
|
aux::g_current_time = now; |
|
// too expensive |
|
// INVARIANT_CHECK; |
|
|
|
// we have to keep ticking the utp socket manager |
|
// until they're all closed |
|
if (m_abort && m_utp_socket_manager.num_sockets() == 0) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
fprintf(stderr, "uTP sockets left: %d\n", m_utp_socket_manager.num_sockets()); |
|
#endif |
|
return; |
|
} |
|
|
|
if (e == asio::error::operation_aborted) return; |
|
|
|
if (e) |
|
{ |
|
#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING |
|
session_log("*** TICK TIMER FAILED %s", e.message().c_str()); |
|
#endif |
|
::abort(); |
|
return; |
|
} |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_tick"); |
|
#endif |
|
error_code ec; |
|
m_timer.expires_at(now + milliseconds(m_settings.tick_interval), ec); |
|
m_timer.async_wait(bind(&session_impl::on_tick, this, _1)); |
|
|
|
m_download_rate.update_quotas(now - m_last_tick); |
|
m_upload_rate.update_quotas(now - m_last_tick); |
|
|
|
m_last_tick = now; |
|
|
|
m_utp_socket_manager.tick(now); |
|
|
|
// only tick the following once per second |
|
if (now - m_last_second_tick < seconds(1)) return; |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_dht_interval_update_torrents < 40 |
|
&& m_dht_interval_update_torrents != m_torrents.size()) |
|
update_dht_announce_interval(); |
|
#endif |
|
|
|
int tick_interval_ms = total_milliseconds(now - m_last_second_tick); |
|
m_last_second_tick = now; |
|
m_tick_residual += tick_interval_ms - 1000; |
|
|
|
int session_time = total_seconds(now - m_created); |
|
if (session_time > 65000) |
|
{ |
|
// we're getting close to the point where our timestamps |
|
// in policy::peer are wrapping. We need to step all counters back |
|
// four hours. This means that any timestamp that refers to a time |
|
// more than 18.2 - 4 = 14.2 hours ago, will be incremented to refer to |
|
// 14.2 hours ago. |
|
|
|
m_created += hours(4); |
|
|
|
const int four_hours = 60 * 60 * 4; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
policy& p = i->second->get_policy(); |
|
for (policy::iterator j = p.begin_peer() |
|
, end(p.end_peer()); j != end; ++j) |
|
{ |
|
policy::peer* pe = *j; |
|
|
|
if (pe->last_optimistically_unchoked < four_hours) |
|
pe->last_optimistically_unchoked = 0; |
|
else |
|
pe->last_optimistically_unchoked -= four_hours; |
|
|
|
if (pe->last_connected < four_hours) |
|
pe->last_connected = 0; |
|
else |
|
pe->last_connected -= four_hours; |
|
} |
|
} |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS |
|
for (ses_extension_list_t::const_iterator i = m_ses_extensions.begin() |
|
, end(m_ses_extensions.end()); i != end; ++i) |
|
{ |
|
TORRENT_TRY { |
|
(*i)->on_tick(); |
|
} TORRENT_CATCH(std::exception&) {} |
|
} |
|
#endif |
|
|
|
// don't do any of the following while we're shutting down |
|
if (m_abort) return; |
|
|
|
// -------------------------------------------------------------- |
|
// RSS feeds |
|
// -------------------------------------------------------------- |
|
if (now > m_next_rss_update) |
|
update_rss_feeds(); |
|
|
|
switch (m_settings.mixed_mode_algorithm) |
|
{ |
|
case session_settings::prefer_tcp: |
|
m_tcp_upload_channel.throttle(0); |
|
m_tcp_download_channel.throttle(0); |
|
break; |
|
case session_settings::peer_proportional: |
|
{ |
|
int num_peers[2][2] = {{0, 0}, {0, 0}}; |
|
for (connection_map::iterator i = m_connections.begin() |
|
, end(m_connections.end());i != end; ++i) |
|
{ |
|
peer_connection& p = *(*i); |
|
if (p.in_handshake()) continue; |
|
int protocol = 0; |
|
if (is_utp(*p.get_socket())) protocol = 1; |
|
|
|
if (p.download_queue().size() + p.request_queue().size() > 0) |
|
++num_peers[protocol][peer_connection::download_channel]; |
|
if (p.upload_queue().size() > 0) |
|
++num_peers[protocol][peer_connection::upload_channel]; |
|
} |
|
|
|
bandwidth_channel* tcp_channel[] = { &m_tcp_upload_channel, &m_tcp_download_channel }; |
|
int stat_rate[] = {m_stat.upload_rate(), m_stat.download_rate() }; |
|
// never throttle below this |
|
int lower_limit[] = {5000, 30000}; |
|
|
|
for (int i = 0; i < 2; ++i) |
|
{ |
|
// if there are no uploading uTP peers, don't throttle TCP up |
|
if (num_peers[1][i] == 0) |
|
{ |
|
tcp_channel[i]->throttle(0); |
|
} |
|
else |
|
{ |
|
if (num_peers[0][i] == 0) num_peers[0][i] = 1; |
|
int total_peers = num_peers[0][i] + num_peers[1][i]; |
|
// this are 64 bits since it's multiplied by the number |
|
// of peers, which otherwise might overflow an int |
|
boost::uint64_t rate = (std::max)(stat_rate[i], lower_limit[i]); |
|
tcp_channel[i]->throttle(int(rate * num_peers[0][i] / total_peers)); |
|
} |
|
} |
|
} |
|
break; |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// auto managed torrent |
|
// -------------------------------------------------------------- |
|
if (!m_paused) m_auto_manage_time_scaler--; |
|
if (m_auto_manage_time_scaler < 0) |
|
{ |
|
// [MF] first torrent is sent to the end of the queue |
|
torrent* first = NULL; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) { |
|
torrent* t = i->second.get(); |
|
TORRENT_ASSERT(t); |
|
|
|
// checking torrents are not subject to auto-management |
|
if (t->state() == torrent_status::checking_files |
|
|| t->state() == torrent_status::queued_for_checking) |
|
{ |
|
if (t->is_auto_managed() && t->is_paused()) t->resume(); |
|
continue; |
|
} |
|
if (t->is_auto_managed() && !t->has_error()) { |
|
if( !first || t->sequence_number() < first->sequence_number() ) |
|
first = t; |
|
} |
|
} |
|
if( first ) |
|
first->set_queue_position((std::numeric_limits<int>::max)()); |
|
|
|
m_auto_manage_time_scaler = settings().auto_manage_interval; |
|
recalculate_auto_managed_torrents(); |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// check for incoming connections that might have timed out |
|
// -------------------------------------------------------------- |
|
|
|
for (connection_map::iterator i = m_connections.begin(); |
|
i != m_connections.end();) |
|
{ |
|
peer_connection* p = (*i).get(); |
|
++i; |
|
// ignore connections that already have a torrent, since they |
|
// are ticked through the torrents' second_tick |
|
if (!p->associated_torrent().expired()) continue; |
|
// TODO: have a separate list for these connections, instead of having to loop through all of them |
|
if (m_last_tick - p->connected_time() > seconds(m_settings.handshake_timeout)) |
|
p->disconnect(errors::timed_out); |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// second_tick every torrent |
|
// -------------------------------------------------------------- |
|
|
|
int congested_torrents = 0; |
|
int uncongested_torrents = 0; |
|
|
|
// count the number of seeding torrents vs. downloading |
|
// torrents we are running |
|
int num_seeds = 0; |
|
int num_downloads = 0; |
|
|
|
// count the number of peers of downloading torrents |
|
int num_downloads_peers = 0; |
|
|
|
torrent_map::iterator least_recently_scraped = m_torrents.end(); |
|
int num_paused_auto_managed = 0; |
|
|
|
int num_checking = 0; |
|
int num_queued = 0; |
|
for (torrent_map::iterator i = m_torrents.begin(); |
|
i != m_torrents.end();) |
|
{ |
|
torrent& t = *i->second; |
|
TORRENT_ASSERT(!t.is_aborted()); |
|
if (t.statistics().upload_rate() * 11 / 10 > t.upload_limit()) |
|
++congested_torrents; |
|
else |
|
++uncongested_torrents; |
|
|
|
if (t.state() == torrent_status::checking_files) ++num_checking; |
|
else if (t.state() == torrent_status::queued_for_checking && !t.is_paused()) ++num_queued; |
|
|
|
if (t.is_auto_managed() && t.is_paused() && !t.has_error()) |
|
{ |
|
++num_paused_auto_managed; |
|
if (least_recently_scraped == m_torrents.end() |
|
|| least_recently_scraped->second->seconds_since_last_scrape() |
|
< t.seconds_since_last_scrape()) |
|
{ |
|
least_recently_scraped = i; |
|
} |
|
} |
|
|
|
if (t.is_finished()) |
|
{ |
|
++num_seeds; |
|
} |
|
else |
|
{ |
|
++num_downloads; |
|
num_downloads_peers += t.num_peers(); |
|
} |
|
|
|
t.second_tick(m_stat, tick_interval_ms); |
|
++i; |
|
} |
|
|
|
// some people claim that there sometimes can be cases where |
|
// there is no torrent being checked, but there are torrents |
|
// waiting to be checked. I have never seen this, and I can't |
|
// see a way for it to happen. But, if it does, start one of |
|
// the queued torrents |
|
if (num_checking == 0 && num_queued > 0 && !m_paused) |
|
{ |
|
TORRENT_ASSERT(false); |
|
check_queue_t::iterator i = std::min_element(m_queued_for_checking.begin() |
|
, m_queued_for_checking.end(), boost::bind(&torrent::queue_position, _1) |
|
< boost::bind(&torrent::queue_position, _2)); |
|
if (i != m_queued_for_checking.end()) |
|
{ |
|
(*i)->start_checking(); |
|
} |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_dht) |
|
{ |
|
int dht_down; |
|
int dht_up; |
|
m_dht->network_stats(dht_up, dht_down); |
|
m_stat.sent_dht_bytes(dht_up); |
|
m_stat.received_dht_bytes(dht_down); |
|
} |
|
#endif |
|
|
|
if (m_settings.rate_limit_ip_overhead) |
|
{ |
|
m_download_channel.use_quota( |
|
#ifndef TORRENT_DISABLE_DHT |
|
m_stat.download_dht() + |
|
#endif |
|
m_stat.download_tracker()); |
|
|
|
m_upload_channel.use_quota( |
|
#ifndef TORRENT_DISABLE_DHT |
|
m_stat.upload_dht() + |
|
#endif |
|
m_stat.upload_tracker()); |
|
|
|
int up_limit = m_upload_channel.throttle(); |
|
int down_limit = m_download_channel.throttle(); |
|
|
|
if (down_limit > 0 |
|
&& m_stat.download_ip_overhead() >= down_limit |
|
&& m_alerts.should_post<performance_alert>()) |
|
{ |
|
m_alerts.post_alert(performance_alert(torrent_handle() |
|
, performance_alert::download_limit_too_low)); |
|
} |
|
|
|
if (up_limit > 0 |
|
&& m_stat.upload_ip_overhead() >= up_limit |
|
&& m_alerts.should_post<performance_alert>()) |
|
{ |
|
m_alerts.post_alert(performance_alert(torrent_handle() |
|
, performance_alert::upload_limit_too_low)); |
|
} |
|
} |
|
|
|
m_peak_up_rate = (std::max)(m_stat.upload_rate(), m_peak_up_rate); |
|
m_peak_down_rate = (std::max)(m_stat.download_rate(), m_peak_down_rate); |
|
|
|
m_stat.second_tick(tick_interval_ms); |
|
|
|
TORRENT_ASSERT(least_recently_scraped == m_torrents.end() |
|
|| (least_recently_scraped->second->is_paused() |
|
&& least_recently_scraped->second->is_auto_managed())); |
|
|
|
#ifdef TORRENT_STATS |
|
|
|
if (m_stats_logging_enabled) |
|
{ |
|
print_log_line(tick_interval_ms, now); |
|
} |
|
#endif |
|
|
|
// -------------------------------------------------------------- |
|
// scrape paused torrents that are auto managed |
|
// (unless the session is paused) |
|
// -------------------------------------------------------------- |
|
if (!is_paused()) |
|
{ |
|
--m_auto_scrape_time_scaler; |
|
if (m_auto_scrape_time_scaler <= 0) |
|
{ |
|
m_auto_scrape_time_scaler = m_settings.auto_scrape_interval |
|
/ (std::max)(1, num_paused_auto_managed); |
|
if (m_auto_scrape_time_scaler < m_settings.auto_scrape_min_interval) |
|
m_auto_scrape_time_scaler = m_settings.auto_scrape_min_interval; |
|
|
|
if (least_recently_scraped != m_torrents.end()) |
|
{ |
|
least_recently_scraped->second->scrape_tracker(); |
|
} |
|
} |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// refresh explicit disk read cache |
|
// -------------------------------------------------------------- |
|
--m_cache_rotation_timer; |
|
if (m_settings.explicit_read_cache |
|
&& m_cache_rotation_timer <= 0) |
|
{ |
|
m_cache_rotation_timer = m_settings.explicit_cache_interval; |
|
|
|
torrent_map::iterator least_recently_refreshed = m_torrents.begin(); |
|
if (m_next_explicit_cache_torrent >= int(m_torrents.size())) |
|
m_next_explicit_cache_torrent = 0; |
|
|
|
std::advance(least_recently_refreshed, m_next_explicit_cache_torrent); |
|
|
|
// how many blocks does this torrent get? |
|
int cache_size = (std::max)(0, m_settings.cache_size * 9 / 10); |
|
|
|
if (m_connections.empty()) |
|
{ |
|
// if we don't have any connections at all, split the |
|
// cache evenly across all torrents |
|
cache_size = cache_size / (std::max)(int(m_torrents.size()), 1); |
|
} |
|
else |
|
{ |
|
cache_size = cache_size * least_recently_refreshed->second->num_peers() |
|
/ m_connections.size(); |
|
} |
|
|
|
if (least_recently_refreshed != m_torrents.end()) |
|
least_recently_refreshed->second->refresh_explicit_cache(cache_size); |
|
++m_next_explicit_cache_torrent; |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// connect new peers |
|
// -------------------------------------------------------------- |
|
|
|
try_connect_more_peers(num_downloads, num_downloads_peers); |
|
|
|
// -------------------------------------------------------------- |
|
// unchoke set calculations |
|
// -------------------------------------------------------------- |
|
m_unchoke_time_scaler--; |
|
if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) |
|
{ |
|
m_unchoke_time_scaler = settings().unchoke_interval; |
|
recalculate_unchoke_slots(congested_torrents |
|
, uncongested_torrents); |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// optimistic unchoke calculation |
|
// -------------------------------------------------------------- |
|
m_optimistic_unchoke_time_scaler--; |
|
if (m_optimistic_unchoke_time_scaler <= 0) |
|
{ |
|
m_optimistic_unchoke_time_scaler |
|
= settings().optimistic_unchoke_interval; |
|
recalculate_optimistic_unchoke_slots(); |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// disconnect peers when we have too many |
|
// -------------------------------------------------------------- |
|
--m_disconnect_time_scaler; |
|
if (m_disconnect_time_scaler <= 0) |
|
{ |
|
m_disconnect_time_scaler = m_settings.peer_turnover_interval; |
|
|
|
if (num_connections() >= m_settings.connections_limit * m_settings.peer_turnover_cutoff |
|
&& !m_torrents.empty()) |
|
{ |
|
// every 90 seconds, disconnect the worst peers |
|
// if we have reached the connection limit |
|
torrent_map::iterator i = std::max_element(m_torrents.begin(), m_torrents.end() |
|
, boost::bind(&torrent::num_peers, boost::bind(&torrent_map::value_type::second, _1)) |
|
< boost::bind(&torrent::num_peers, boost::bind(&torrent_map::value_type::second, _2))); |
|
|
|
TORRENT_ASSERT(i != m_torrents.end()); |
|
int peers_to_disconnect = (std::min)((std::max)( |
|
int(i->second->num_peers() * m_settings.peer_turnover), 1) |
|
, i->second->get_policy().num_connect_candidates()); |
|
i->second->disconnect_peers(peers_to_disconnect |
|
, error_code(errors::optimistic_disconnect, get_libtorrent_category())); |
|
} |
|
else |
|
{ |
|
// if we haven't reached the global max. see if any torrent |
|
// has reached its local limit |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
boost::shared_ptr<torrent> t = i->second; |
|
if (t->num_peers() < t->max_connections() * m_settings.peer_turnover_cutoff) |
|
continue; |
|
|
|
int peers_to_disconnect = (std::min)((std::max)(int(i->second->num_peers() |
|
* m_settings.peer_turnover), 1) |
|
, i->second->get_policy().num_connect_candidates()); |
|
t->disconnect_peers(peers_to_disconnect |
|
, error_code(errors::optimistic_disconnect, get_libtorrent_category())); |
|
} |
|
} |
|
} |
|
|
|
while (m_tick_residual >= 1000) m_tick_residual -= 1000; |
|
// m_peer_pool.release_memory(); |
|
} |
|
|
|
#ifdef TORRENT_STATS |
|
|
|
void session_impl::enable_stats_logging(bool s) |
|
{ |
|
if (m_stats_logging_enabled == s) return; |
|
|
|
m_stats_logging_enabled = s; |
|
|
|
reset_stat_counters(); |
|
if (!s) |
|
{ |
|
if (m_stats_logger) fclose(m_stats_logger); |
|
m_stats_logger = 0; |
|
} |
|
else |
|
{ |
|
rotate_stats_log(); |
|
get_thread_cpu_usage(&m_network_thread_cpu_usage); |
|
} |
|
} |
|
|
|
void session_impl::reset_stat_counters() |
|
{ |
|
m_end_game_piece_picker_blocks = 0; |
|
m_piece_picker_blocks = 0; |
|
m_piece_picks = 0; |
|
m_reject_piece_picks = 0; |
|
m_unchoke_piece_picks = 0; |
|
m_incoming_redundant_piece_picks = 0; |
|
m_incoming_piece_picks = 0; |
|
m_end_game_piece_picks = 0; |
|
m_snubbed_piece_picks = 0; |
|
m_connection_attempts = 0; |
|
m_num_banned_peers = 0; |
|
m_banned_for_hash_failure = 0; |
|
|
|
m_piece_requests = 0; |
|
m_max_piece_requests = 0; |
|
m_invalid_piece_requests = 0; |
|
m_choked_piece_requests = 0; |
|
m_cancelled_piece_requests = 0; |
|
m_piece_rejects = 0; |
|
|
|
memset(m_num_messages, 0, sizeof(m_num_messages)); |
|
memset(m_send_buffer_sizes, 0, sizeof(m_send_buffer_sizes)); |
|
memset(m_recv_buffer_sizes, 0, sizeof(m_recv_buffer_sizes)); |
|
} |
|
|
|
void session_impl::print_log_line(int tick_interval_ms, ptime now) |
|
{ |
|
int connect_candidates = 0; |
|
|
|
int checking_torrents = 0; |
|
int stopped_torrents = 0; |
|
int upload_only_torrents = 0; |
|
int downloading_torrents = 0; |
|
int seeding_torrents = 0; |
|
int queued_seed_torrents = 0; |
|
int queued_download_torrents = 0; |
|
int error_torrents = 0; |
|
|
|
int num_peers = 0; |
|
int peer_dl_rate_buckets[7]; |
|
int peer_ul_rate_buckets[7]; |
|
memset(peer_dl_rate_buckets, 0, sizeof(peer_dl_rate_buckets)); |
|
memset(peer_ul_rate_buckets, 0, sizeof(peer_ul_rate_buckets)); |
|
int outstanding_requests = 0; |
|
int outstanding_end_game_requests = 0; |
|
int outstanding_write_blocks = 0; |
|
|
|
int peers_up_interested = 0; |
|
int peers_down_interesting = 0; |
|
int peers_up_requests = 0; |
|
int peers_down_requests = 0; |
|
int peers_up_send_buffer = 0; |
|
|
|
// number of torrents that want more peers |
|
int num_want_more_peers = 0; |
|
|
|
// number of peers among torrents with a peer limit |
|
int num_limited_peers = 0; |
|
// sum of limits of all torrents with a peer limit |
|
int total_peers_limit = 0; |
|
|
|
std::vector<partial_piece_info> dq; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
torrent* t = i->second.get(); |
|
int connection_slots = (std::max)(t->max_connections() - t->num_peers(), 0); |
|
int candidates = t->get_policy().num_connect_candidates(); |
|
connect_candidates += (std::min)(candidates, connection_slots); |
|
num_peers += t->get_policy().num_peers(); |
|
|
|
if (t->want_more_peers()) ++num_want_more_peers; |
|
if (t->max_connections() > 0) |
|
{ |
|
num_limited_peers += t->num_peers(); |
|
num_limited_peers += t->max_connections(); |
|
} |
|
|
|
if (t->has_error()) |
|
++error_torrents; |
|
else |
|
{ |
|
if (t->is_paused()) |
|
{ |
|
if (!t->is_auto_managed()) |
|
++stopped_torrents; |
|
else |
|
{ |
|
if (t->is_seed()) |
|
++queued_seed_torrents; |
|
else |
|
++queued_download_torrents; |
|
} |
|
} |
|
else |
|
{ |
|
if (i->second->state() == torrent_status::checking_files |
|
|| i->second->state() == torrent_status::queued_for_checking) |
|
++checking_torrents; |
|
else if (i->second->is_seed()) |
|
++seeding_torrents; |
|
else if (i->second->is_upload_only()) |
|
++upload_only_torrents; |
|
else |
|
++downloading_torrents; |
|
} |
|
} |
|
|
|
dq.clear(); |
|
i->second->get_download_queue(&dq); |
|
for (std::vector<partial_piece_info>::iterator j = dq.begin() |
|
, end(dq.end()); j != end; ++j) |
|
{ |
|
for (int k = 0; k < j->blocks_in_piece; ++k) |
|
{ |
|
block_info& bi = j->blocks[k]; |
|
if (bi.state == block_info::requested) |
|
{ |
|
++outstanding_requests; |
|
if (bi.num_peers > 1) ++outstanding_end_game_requests; |
|
} |
|
else if (bi.state == block_info::writing) |
|
++outstanding_write_blocks; |
|
} |
|
} |
|
} |
|
int tcp_up_rate = 0; |
|
int tcp_down_rate = 0; |
|
int utp_up_rate = 0; |
|
int utp_down_rate = 0; |
|
int utp_peak_send_delay = 0; |
|
int utp_peak_recv_delay = 0; |
|
boost::uint64_t utp_send_delay_sum = 0; |
|
boost::uint64_t utp_recv_delay_sum = 0; |
|
int num_utp_peers = 0; |
|
int num_tcp_peers = 0; |
|
int utp_num_delay_sockets = 0; |
|
int utp_num_recv_delay_sockets = 0; |
|
int num_complete_connections = 0; |
|
int num_half_open = 0; |
|
int peers_down_unchoked = 0; |
|
int peers_up_unchoked = 0; |
|
int num_end_game_peers = 0; |
|
int reading_bytes = 0; |
|
int pending_incoming_reqs = 0; |
|
|
|
for (connection_map::iterator i = m_connections.begin() |
|
, end(m_connections.end()); i != end; ++i) |
|
{ |
|
peer_connection* p = i->get(); |
|
if (p->is_connecting()) |
|
{ |
|
++num_half_open; |
|
continue; |
|
} |
|
|
|
++num_complete_connections; |
|
if (!p->is_choked()) ++peers_up_unchoked; |
|
if (!p->has_peer_choked()) ++peers_down_unchoked; |
|
if (!p->download_queue().empty()) ++peers_down_requests; |
|
if (p->is_peer_interested()) ++peers_up_interested; |
|
if (p->is_interesting()) ++peers_down_interesting; |
|
if (p->send_buffer_size() > 100 || !p->upload_queue().empty() || p->num_reading_bytes() > 0) |
|
++peers_up_requests; |
|
if (p->endgame()) ++num_end_game_peers; |
|
reading_bytes += p->num_reading_bytes(); |
|
|
|
pending_incoming_reqs += int(p->upload_queue().size()); |
|
|
|
int dl_bucket = 0; |
|
int dl_rate = p->statistics().download_payload_rate(); |
|
if (dl_rate == 0) dl_bucket = 0; |
|
else if (dl_rate < 2000) dl_bucket = 1; |
|
else if (dl_rate < 5000) dl_bucket = 2; |
|
else if (dl_rate < 10000) dl_bucket = 3; |
|
else if (dl_rate < 50000) dl_bucket = 4; |
|
else if (dl_rate < 100000) dl_bucket = 5; |
|
else dl_bucket = 6; |
|
|
|
int ul_rate = p->statistics().upload_payload_rate(); |
|
int ul_bucket = 0; |
|
if (ul_rate == 0) ul_bucket = 0; |
|
else if (ul_rate < 2000) ul_bucket = 1; |
|
else if (ul_rate < 5000) ul_bucket = 2; |
|
else if (ul_rate < 10000) ul_bucket = 3; |
|
else if (ul_rate < 50000) ul_bucket = 4; |
|
else if (ul_rate < 100000) ul_bucket = 5; |
|
else ul_bucket = 6; |
|
|
|
++peer_dl_rate_buckets[dl_bucket]; |
|
++peer_ul_rate_buckets[ul_bucket]; |
|
|
|
boost::uint64_t upload_rate = int(p->statistics().upload_rate()); |
|
int buffer_size_watermark = upload_rate |
|
* m_settings.send_buffer_watermark_factor / 100; |
|
if (buffer_size_watermark < m_settings.send_buffer_low_watermark) |
|
buffer_size_watermark = m_settings.send_buffer_low_watermark; |
|
else if (buffer_size_watermark > m_settings.send_buffer_watermark) |
|
buffer_size_watermark = m_settings.send_buffer_watermark; |
|
if (p->send_buffer_size() + p->num_reading_bytes() >= buffer_size_watermark) |
|
++peers_up_send_buffer; |
|
|
|
utp_stream* utp_socket = p->get_socket()->get<utp_stream>(); |
|
#ifdef TORRENT_USE_OPENSSL |
|
if (!utp_socket) |
|
{ |
|
ssl_stream<utp_stream>* ssl_str = p->get_socket()->get<ssl_stream<utp_stream> >(); |
|
if (ssl_str) utp_socket = &ssl_str->next_layer(); |
|
} |
|
#endif |
|
if (utp_socket) |
|
{ |
|
utp_up_rate += ul_rate; |
|
utp_down_rate += dl_rate; |
|
int send_delay = utp_socket->send_delay(); |
|
int recv_delay = utp_socket->recv_delay(); |
|
utp_peak_send_delay = (std::max)(utp_peak_send_delay, send_delay); |
|
utp_peak_recv_delay = (std::max)(utp_peak_recv_delay, recv_delay); |
|
if (send_delay > 0) |
|
{ |
|
utp_send_delay_sum += send_delay; |
|
++utp_num_delay_sockets; |
|
} |
|
if (recv_delay > 0) |
|
{ |
|
utp_recv_delay_sum += recv_delay; |
|
++utp_num_recv_delay_sockets; |
|
} |
|
++num_utp_peers; |
|
} |
|
else |
|
{ |
|
tcp_up_rate += ul_rate; |
|
tcp_down_rate += dl_rate; |
|
++num_tcp_peers; |
|
} |
|
|
|
} |
|
|
|
int low_watermark = m_settings.max_queued_disk_bytes_low_watermark == 0 |
|
|| m_settings.max_queued_disk_bytes_low_watermark >= m_settings.max_queued_disk_bytes |
|
? size_type(m_settings.max_queued_disk_bytes) * 7 / 8 |
|
: m_settings.max_queued_disk_bytes_low_watermark; |
|
|
|
if (now - m_last_log_rotation > hours(1)) |
|
rotate_stats_log(); |
|
|
|
// system memory stats |
|
vm_statistics_data_t vm_stat; |
|
get_vm_stats(&vm_stat); |
|
thread_cpu_usage cur_cpu_usage; |
|
get_thread_cpu_usage(&cur_cpu_usage); |
|
|
|
if (m_stats_logger) |
|
{ |
|
cache_status cs = m_disk_thread.status(); |
|
session_status sst = status(); |
|
|
|
m_read_ops.add_sample((cs.reads - m_last_cache_status.reads) * 1000.0 / float(tick_interval_ms)); |
|
m_write_ops.add_sample((cs.writes - m_last_cache_status.writes) * 1000.0 / float(tick_interval_ms)); |
|
|
|
int total_job_time = cs.cumulative_job_time == 0 ? 1 : cs.cumulative_job_time; |
|
|
|
#define STAT_LOG(type, val) fprintf(m_stats_logger, "%" #type "\t", val) |
|
|
|
STAT_LOG(f, total_milliseconds(now - m_last_log_rotation) / 1000.f); |
|
size_type uploaded = m_stat.total_upload() - m_last_uploaded; |
|
STAT_LOG(d, int(uploaded)); |
|
size_type downloaded = m_stat.total_download() - m_last_downloaded; |
|
STAT_LOG(d, int(downloaded)); |
|
STAT_LOG(d, downloading_torrents); |
|
STAT_LOG(d, seeding_torrents); |
|
STAT_LOG(d, num_complete_connections); |
|
STAT_LOG(d, num_half_open); |
|
STAT_LOG(d, m_disk_thread.disk_allocations()); |
|
STAT_LOG(d, num_peers); |
|
STAT_LOG(d, logging_allocator::allocations); |
|
STAT_LOG(d, logging_allocator::allocated_bytes); |
|
STAT_LOG(d, checking_torrents); |
|
STAT_LOG(d, stopped_torrents); |
|
STAT_LOG(d, upload_only_torrents); |
|
STAT_LOG(d, queued_seed_torrents); |
|
STAT_LOG(d, queued_download_torrents); |
|
STAT_LOG(d, m_upload_rate.queue_size()); |
|
STAT_LOG(d, m_download_rate.queue_size()); |
|
STAT_LOG(d, m_disk_queues[peer_connection::upload_channel]); |
|
STAT_LOG(d, m_disk_queues[peer_connection::download_channel]); |
|
STAT_LOG(d, m_stat.upload_rate()); |
|
STAT_LOG(d, m_stat.download_rate()); |
|
STAT_LOG(d, int(m_disk_thread.queue_buffer_size())); |
|
STAT_LOG(d, peer_dl_rate_buckets[0]); |
|
STAT_LOG(d, peer_dl_rate_buckets[1]); |
|
STAT_LOG(d, peer_dl_rate_buckets[2]); |
|
STAT_LOG(d, peer_dl_rate_buckets[3]); |
|
STAT_LOG(d, peer_dl_rate_buckets[4]); |
|
STAT_LOG(d, peer_dl_rate_buckets[5]); |
|
STAT_LOG(d, peer_dl_rate_buckets[6]); |
|
STAT_LOG(d, peer_ul_rate_buckets[0]); |
|
STAT_LOG(d, peer_ul_rate_buckets[1]); |
|
STAT_LOG(d, peer_ul_rate_buckets[2]); |
|
STAT_LOG(d, peer_ul_rate_buckets[3]); |
|
STAT_LOG(d, peer_ul_rate_buckets[4]); |
|
STAT_LOG(d, peer_ul_rate_buckets[5]); |
|
STAT_LOG(d, peer_ul_rate_buckets[6]); |
|
STAT_LOG(d, m_error_peers); |
|
STAT_LOG(d, peers_down_interesting); |
|
STAT_LOG(d, peers_down_unchoked); |
|
STAT_LOG(d, peers_down_requests); |
|
STAT_LOG(d, peers_up_interested); |
|
STAT_LOG(d, peers_up_unchoked); |
|
STAT_LOG(d, peers_up_requests); |
|
STAT_LOG(d, m_disconnected_peers); |
|
STAT_LOG(d, m_eof_peers); |
|
STAT_LOG(d, m_connreset_peers); |
|
STAT_LOG(d, outstanding_requests); |
|
STAT_LOG(d, outstanding_end_game_requests); |
|
STAT_LOG(d, outstanding_write_blocks); |
|
STAT_LOG(d, m_end_game_piece_picker_blocks); |
|
STAT_LOG(d, m_piece_picker_blocks); |
|
STAT_LOG(d, m_piece_picks); |
|
STAT_LOG(d, m_reject_piece_picks); |
|
STAT_LOG(d, m_unchoke_piece_picks); |
|
STAT_LOG(d, m_incoming_redundant_piece_picks); |
|
STAT_LOG(d, m_incoming_piece_picks); |
|
STAT_LOG(d, m_end_game_piece_picks); |
|
STAT_LOG(d, m_snubbed_piece_picks); |
|
STAT_LOG(d, m_connect_timeouts); |
|
STAT_LOG(d, m_uninteresting_peers); |
|
STAT_LOG(d, m_timeout_peers); |
|
STAT_LOG(f, (float(m_total_failed_bytes) * 100.f / (m_stat.total_payload_download() == 0 ? 1 : m_stat.total_payload_download()))); |
|
STAT_LOG(f, (float(m_total_redundant_bytes) * 100.f / (m_stat.total_payload_download() == 0 ? 1 : m_stat.total_payload_download()))); |
|
STAT_LOG(f, (float(m_stat.total_protocol_download()) * 100.f / (m_stat.total_download() == 0 ? 1 : m_stat.total_download()))); |
|
STAT_LOG(f, float(cs.average_read_time) / 1000000.f); |
|
STAT_LOG(f, float(cs.average_write_time) / 1000000.f); |
|
STAT_LOG(f, float(cs.average_queue_time) / 1000000.f); |
|
STAT_LOG(d, int(cs.job_queue_length)); |
|
STAT_LOG(d, int(cs.queued_bytes)); |
|
STAT_LOG(d, int(cs.blocks_read_hit - m_last_cache_status.blocks_read_hit)); |
|
STAT_LOG(d, int(cs.blocks_read - m_last_cache_status.blocks_read)); |
|
STAT_LOG(d, int(cs.blocks_written - m_last_cache_status.blocks_written)); |
|
STAT_LOG(d, int(m_total_failed_bytes - m_last_failed)); |
|
STAT_LOG(d, int(m_total_redundant_bytes - m_last_redundant)); |
|
STAT_LOG(d, error_torrents); |
|
STAT_LOG(d, cs.read_cache_size); |
|
STAT_LOG(d, cs.cache_size); |
|
STAT_LOG(d, cs.total_used_buffers); |
|
STAT_LOG(f, float(cs.average_hash_time) / 1000000.f); |
|
STAT_LOG(f, float(cs.average_job_time) / 1000000.f); |
|
STAT_LOG(f, float(cs.average_sort_time) / 1000000.f); |
|
STAT_LOG(d, m_connection_attempts); |
|
STAT_LOG(d, m_num_banned_peers); |
|
STAT_LOG(d, m_banned_for_hash_failure); |
|
STAT_LOG(d, m_settings.cache_size); |
|
STAT_LOG(d, m_settings.connections_limit); |
|
STAT_LOG(d, connect_candidates); |
|
STAT_LOG(d, int(m_settings.max_queued_disk_bytes)); |
|
STAT_LOG(d, low_watermark); |
|
STAT_LOG(f, float(cs.cumulative_read_time * 100.f / total_job_time)); |
|
STAT_LOG(f, float(cs.cumulative_write_time * 100.f / total_job_time)); |
|
STAT_LOG(f, float(cs.cumulative_hash_time * 100.f / total_job_time)); |
|
STAT_LOG(f, float(cs.cumulative_sort_time * 100.f / total_job_time)); |
|
STAT_LOG(d, int(cs.total_read_back - m_last_cache_status.total_read_back)); |
|
STAT_LOG(f, float(cs.total_read_back * 100.f / (cs.blocks_written == 0 ? 1: cs.blocks_written))); |
|
STAT_LOG(d, cs.read_queue_size); |
|
STAT_LOG(f, float(tick_interval_ms) / 1000.f); |
|
STAT_LOG(f, float(m_tick_residual) / 1000.f); |
|
STAT_LOG(d, m_allowed_upload_slots); |
|
STAT_LOG(d, m_settings.unchoke_slots_limit * 2); |
|
STAT_LOG(d, m_stat.low_pass_upload_rate()); |
|
STAT_LOG(d, m_stat.low_pass_download_rate()); |
|
STAT_LOG(d, num_end_game_peers); |
|
STAT_LOG(d, tcp_up_rate); |
|
STAT_LOG(d, tcp_down_rate); |
|
STAT_LOG(d, int(m_tcp_upload_channel.throttle())); |
|
STAT_LOG(d, int(m_tcp_download_channel.throttle())); |
|
STAT_LOG(d, utp_up_rate); |
|
STAT_LOG(d, utp_down_rate); |
|
STAT_LOG(f, float(utp_peak_send_delay) / 1000000.f); |
|
STAT_LOG(f, float(utp_num_delay_sockets ? float(utp_send_delay_sum) / float(utp_num_delay_sockets) : 0) / 1000000.f); |
|
STAT_LOG(f, float(utp_peak_recv_delay) / 1000000.f); |
|
STAT_LOG(f, float(utp_num_recv_delay_sockets ? float(utp_recv_delay_sum) / float(utp_num_recv_delay_sockets) : 0) / 1000000.f); |
|
STAT_LOG(f, float(cs.reads - m_last_cache_status.reads) * 1000.0 / float(tick_interval_ms)); |
|
STAT_LOG(f, float(cs.writes - m_last_cache_status.writes) * 1000.0 / float(tick_interval_ms)); |
|
|
|
STAT_LOG(d, int(vm_stat.active_count)); |
|
STAT_LOG(d, int(vm_stat.inactive_count)); |
|
STAT_LOG(d, int(vm_stat.wire_count)); |
|
STAT_LOG(d, int(vm_stat.free_count)); |
|
STAT_LOG(d, int(vm_stat.pageins - m_last_vm_stat.pageins)); |
|
STAT_LOG(d, int(vm_stat.pageouts - m_last_vm_stat.pageouts)); |
|
STAT_LOG(d, int(vm_stat.faults - m_last_vm_stat.faults)); |
|
|
|
STAT_LOG(d, m_read_ops.mean()); |
|
STAT_LOG(d, m_write_ops.mean()); |
|
|
|
STAT_LOG(d, reading_bytes); |
|
|
|
for (int i = 0; i < max_messages; ++i) |
|
STAT_LOG(d, m_num_messages[i]); |
|
int num_max = sizeof(m_send_buffer_sizes)/sizeof(m_send_buffer_sizes[0]); |
|
for (int i = 0; i < num_max; ++i) |
|
STAT_LOG(d, m_send_buffer_sizes[i]); |
|
num_max = sizeof(m_recv_buffer_sizes)/sizeof(m_recv_buffer_sizes[0]); |
|
for (int i = 0; i < num_max; ++i) |
|
STAT_LOG(d, m_recv_buffer_sizes[i]); |
|
|
|
STAT_LOG(f, total_microseconds(cur_cpu_usage.user_time |
|
- m_network_thread_cpu_usage.user_time) / double(tick_interval_ms * 10)); |
|
STAT_LOG(f, (total_microseconds(cur_cpu_usage.system_time |
|
- m_network_thread_cpu_usage.system_time) |
|
+ total_microseconds(cur_cpu_usage.user_time |
|
- m_network_thread_cpu_usage.user_time)) |
|
/ double(tick_interval_ms * 10)); |
|
|
|
for (int i = 0; i < torrent::waste_reason_max; ++i) |
|
STAT_LOG(f, (m_redundant_bytes[i] * 100.) / double(m_total_redundant_bytes == 0 ? 1 : m_total_redundant_bytes)); |
|
|
|
STAT_LOG(d, m_no_memory_peers); |
|
STAT_LOG(d, m_too_many_peers); |
|
STAT_LOG(d, m_transport_timeout_peers); |
|
|
|
STAT_LOG(d, sst.utp_stats.num_idle); |
|
STAT_LOG(d, sst.utp_stats.num_syn_sent); |
|
STAT_LOG(d, sst.utp_stats.num_connected); |
|
STAT_LOG(d, sst.utp_stats.num_fin_sent); |
|
STAT_LOG(d, sst.utp_stats.num_close_wait); |
|
|
|
STAT_LOG(d, num_tcp_peers); |
|
STAT_LOG(d, num_utp_peers); |
|
|
|
STAT_LOG(d, m_connrefused_peers); |
|
STAT_LOG(d, m_connaborted_peers); |
|
STAT_LOG(d, m_perm_peers); |
|
STAT_LOG(d, m_buffer_peers); |
|
STAT_LOG(d, m_unreachable_peers); |
|
STAT_LOG(d, m_broken_pipe_peers); |
|
STAT_LOG(d, m_addrinuse_peers); |
|
STAT_LOG(d, m_no_access_peers); |
|
STAT_LOG(d, m_invalid_arg_peers); |
|
STAT_LOG(d, m_aborted_peers); |
|
|
|
STAT_LOG(d, m_error_incoming_peers); |
|
STAT_LOG(d, m_error_outgoing_peers); |
|
STAT_LOG(d, m_error_rc4_peers); |
|
STAT_LOG(d, m_error_encrypted_peers); |
|
STAT_LOG(d, m_error_tcp_peers); |
|
STAT_LOG(d, m_error_utp_peers); |
|
|
|
STAT_LOG(d, int(m_connections.size())); |
|
STAT_LOG(d, pending_incoming_reqs); |
|
STAT_LOG(f, num_complete_connections == 0 ? 0.f : (float(pending_incoming_reqs) / num_complete_connections)); |
|
|
|
STAT_LOG(d, num_want_more_peers); |
|
STAT_LOG(f, total_peers_limit == 0 ? 0 : float(num_limited_peers) / total_peers_limit); |
|
|
|
STAT_LOG(d, m_piece_requests); |
|
STAT_LOG(d, m_max_piece_requests); |
|
STAT_LOG(d, m_invalid_piece_requests); |
|
STAT_LOG(d, m_choked_piece_requests); |
|
STAT_LOG(d, m_cancelled_piece_requests); |
|
STAT_LOG(d, m_piece_rejects); |
|
|
|
STAT_LOG(d, peers_up_send_buffer); |
|
|
|
fprintf(m_stats_logger, "\n"); |
|
|
|
#undef STAT_LOG |
|
|
|
m_last_cache_status = cs; |
|
m_last_vm_stat = vm_stat; |
|
m_network_thread_cpu_usage = cur_cpu_usage; |
|
m_last_failed = m_total_failed_bytes; |
|
m_last_redundant = m_total_redundant_bytes; |
|
m_last_uploaded = m_stat.total_upload(); |
|
m_last_downloaded = m_stat.total_download(); |
|
} |
|
|
|
reset_stat_counters(); |
|
} |
|
#endif // TORRENT_STATS |
|
|
|
void session_impl::update_rss_feeds() |
|
{ |
|
time_t now_posix = time(0); |
|
ptime min_update = max_time(); |
|
ptime now = time_now(); |
|
for (std::vector<boost::shared_ptr<feed> >::iterator i |
|
= m_feeds.begin(), end(m_feeds.end()); i != end; ++i) |
|
{ |
|
feed& f = **i; |
|
int delta = f.next_update(now_posix); |
|
if (delta <= 0) |
|
delta = f.update_feed(); |
|
TORRENT_ASSERT(delta >= 0); |
|
ptime next_update = now + seconds(delta); |
|
if (next_update < min_update) min_update = next_update; |
|
} |
|
m_next_rss_update = min_update; |
|
} |
|
|
|
void session_impl::prioritize_connections(boost::weak_ptr<torrent> t) |
|
{ |
|
m_prio_torrents.push_back(std::make_pair(t, 10)); |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
|
|
void session_impl::add_dht_node(udp::endpoint n) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_dht) m_dht->add_node(n); |
|
} |
|
|
|
void session_impl::prioritize_dht(boost::weak_ptr<torrent> t) |
|
{ |
|
m_dht_torrents.push_back(t); |
|
// trigger a DHT announce right away if we just |
|
// added a new torrent and there's no back-log |
|
if (m_dht_torrents.size() == 1) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_dht_announce"); |
|
#endif |
|
error_code ec; |
|
m_dht_announce_timer.expires_from_now(seconds(0), ec); |
|
m_dht_announce_timer.async_wait( |
|
bind(&session_impl::on_dht_announce, this, _1)); |
|
} |
|
} |
|
|
|
void session_impl::on_dht_announce(error_code const& e) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_dht_announce"); |
|
#endif |
|
TORRENT_ASSERT(is_network_thread()); |
|
if (e) return; |
|
|
|
if (m_abort) return; |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_dht_announce"); |
|
#endif |
|
// announce to DHT every 15 minutes |
|
int delay = (std::max)(m_settings.dht_announce_interval |
|
/ (std::max)(int(m_torrents.size()), 1), 1); |
|
|
|
if (!m_dht_torrents.empty()) |
|
{ |
|
// we have prioritized torrents that need |
|
// an initial DHT announce. Don't wait too long |
|
// until we announce those. |
|
delay = (std::min)(4, delay); |
|
} |
|
|
|
error_code ec; |
|
m_dht_announce_timer.expires_from_now(seconds(delay), ec); |
|
m_dht_announce_timer.async_wait( |
|
bind(&session_impl::on_dht_announce, this, _1)); |
|
|
|
if (!m_dht_torrents.empty()) |
|
{ |
|
boost::shared_ptr<torrent> t; |
|
do |
|
{ |
|
t = m_dht_torrents.front().lock(); |
|
m_dht_torrents.pop_front(); |
|
} |
|
while (!t && !m_dht_torrents.empty()); |
|
if (t) |
|
{ |
|
t->dht_announce(); |
|
return; |
|
} |
|
} |
|
if (m_torrents.empty()) return; |
|
|
|
if (m_next_dht_torrent == m_torrents.end()) |
|
m_next_dht_torrent = m_torrents.begin(); |
|
m_next_dht_torrent->second->dht_announce(); |
|
++m_next_dht_torrent; |
|
if (m_next_dht_torrent == m_torrents.end()) |
|
m_next_dht_torrent = m_torrents.begin(); |
|
} |
|
#endif |
|
|
|
void session_impl::on_lsd_announce(error_code const& e) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_lsd_announce"); |
|
#endif |
|
#ifdef TORRENT_STATS |
|
++m_num_messages[on_lsd_counter]; |
|
#endif |
|
TORRENT_ASSERT(is_network_thread()); |
|
if (e) return; |
|
|
|
if (m_abort) return; |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_lsd_announce"); |
|
#endif |
|
// announce on local network every 5 minutes |
|
int delay = (std::max)(m_settings.local_service_announce_interval |
|
/ (std::max)(int(m_torrents.size()), 1), 1); |
|
error_code ec; |
|
m_lsd_announce_timer.expires_from_now(seconds(delay), ec); |
|
m_lsd_announce_timer.async_wait( |
|
bind(&session_impl::on_lsd_announce, this, _1)); |
|
|
|
if (m_torrents.empty()) return; |
|
|
|
if (m_next_lsd_torrent == m_torrents.end()) |
|
m_next_lsd_torrent = m_torrents.begin(); |
|
m_next_lsd_torrent->second->lsd_announce(); |
|
++m_next_lsd_torrent; |
|
if (m_next_lsd_torrent == m_torrents.end()) |
|
m_next_lsd_torrent = m_torrents.begin(); |
|
} |
|
|
|
namespace |
|
{ |
|
bool is_active(torrent* t, session_settings const& s) |
|
{ |
|
// if we count slow torrents, every torrent |
|
// is considered active |
|
if (!s.dont_count_slow_torrents) return true; |
|
|
|
// if the torrent started less than 2 minutes |
|
// ago (default), let it count as active since |
|
// the rates are probably not accurate yet |
|
if (time_now() - t->started() < seconds(s.auto_manage_startup)) return true; |
|
|
|
return t->statistics().upload_payload_rate() != 0.f |
|
|| t->statistics().download_payload_rate() != 0.f; |
|
} |
|
} |
|
|
|
void session_impl::auto_manage_torrents(std::vector<torrent*>& list |
|
, int& dht_limit, int& tracker_limit, int& lsd_limit |
|
, int& hard_limit, int type_limit) |
|
{ |
|
for (std::vector<torrent*>::iterator i = list.begin() |
|
, end(list.end()); i != end; ++i) |
|
{ |
|
torrent* t = *i; |
|
|
|
if ((t->state() == torrent_status::checking_files |
|
|| t->state() == torrent_status::queued_for_checking)) |
|
continue; |
|
|
|
--dht_limit; |
|
--lsd_limit; |
|
--tracker_limit; |
|
t->set_announce_to_dht(dht_limit >= 0); |
|
t->set_announce_to_trackers(tracker_limit >= 0); |
|
t->set_announce_to_lsd(lsd_limit >= 0); |
|
|
|
if (!t->is_paused() && !is_active(t, settings()) |
|
&& hard_limit > 0) |
|
{ |
|
--hard_limit; |
|
continue; |
|
} |
|
|
|
if (type_limit > 0 && hard_limit > 0) |
|
{ |
|
--hard_limit; |
|
--type_limit; |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
//t->log_to_all_peers("AUTO MANAGER STARTING TORRENT"); |
|
#endif |
|
t->set_allow_peers(true); |
|
} |
|
else |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
//t->log_to_all_peers("AUTO MANAGER PAUSING TORRENT"); |
|
#endif |
|
// use graceful pause for auto-managed torrents |
|
t->set_allow_peers(false, true); |
|
} |
|
} |
|
} |
|
|
|
void session_impl::recalculate_auto_managed_torrents() |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
m_need_auto_manage = false; |
|
|
|
// these vectors are filled with auto managed torrents |
|
std::vector<torrent*> downloaders; |
|
downloaders.reserve(m_torrents.size()); |
|
std::vector<torrent*> seeds; |
|
seeds.reserve(m_torrents.size()); |
|
|
|
// these counters are set to the number of torrents |
|
// of each kind we're allowed to have active |
|
int num_downloaders = settings().active_downloads; |
|
int num_seeds = settings().active_seeds; |
|
int dht_limit = settings().active_dht_limit; |
|
int tracker_limit = settings().active_tracker_limit; |
|
int lsd_limit = settings().active_lsd_limit; |
|
int hard_limit = settings().active_limit; |
|
|
|
if (num_downloaders == -1) |
|
num_downloaders = (std::numeric_limits<int>::max)(); |
|
if (num_seeds == -1) |
|
num_seeds = (std::numeric_limits<int>::max)(); |
|
if (hard_limit == -1) |
|
hard_limit = (std::numeric_limits<int>::max)(); |
|
if (dht_limit == -1) |
|
dht_limit = (std::numeric_limits<int>::max)(); |
|
if (lsd_limit == -1) |
|
lsd_limit = (std::numeric_limits<int>::max)(); |
|
if (tracker_limit == -1) |
|
tracker_limit = (std::numeric_limits<int>::max)(); |
|
|
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
torrent* t = i->second.get(); |
|
TORRENT_ASSERT(t); |
|
|
|
// checking torrents are not subject to auto-management |
|
if (t->state() == torrent_status::checking_files |
|
|| t->state() == torrent_status::queued_for_checking) |
|
{ |
|
if (t->is_auto_managed() && t->is_paused()) t->resume(); |
|
continue; |
|
} |
|
if (t->is_auto_managed() && !t->has_error()) |
|
{ |
|
TORRENT_ASSERT(t->m_resume_data_loaded || !t->valid_metadata()); |
|
// this torrent is auto managed, add it to |
|
// the list (depending on if it's a seed or not) |
|
if (t->is_finished()) |
|
seeds.push_back(t); |
|
else |
|
downloaders.push_back(t); |
|
} |
|
//else if (!t->is_paused()) |
|
// [MF] don't check non-auto managed torrents against limits for now. |
|
// FIXME: another strategy is required as definitive fix. |
|
else if (!t->is_paused() && t->is_auto_managed()) |
|
{ |
|
TORRENT_ASSERT(t->m_resume_data_loaded || !t->valid_metadata()); |
|
--hard_limit; |
|
if (is_active(t, settings())) |
|
{ |
|
// this is not an auto managed torrent, |
|
// if it's running and active, decrease the |
|
// counters. |
|
if (t->is_finished()) |
|
--num_seeds; |
|
else |
|
--num_downloaders; |
|
} |
|
} |
|
} |
|
|
|
bool handled_by_extension = false; |
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS |
|
// TODO: 0 allow extensions to sort torrents for queuing |
|
#endif |
|
|
|
if (!handled_by_extension) |
|
{ |
|
std::sort(downloaders.begin(), downloaders.end() |
|
, boost::bind(&torrent::sequence_number, _1) < boost::bind(&torrent::sequence_number, _2)); |
|
|
|
std::sort(seeds.begin(), seeds.end() |
|
, boost::bind(&torrent::seed_rank, _1, boost::ref(m_settings)) |
|
> boost::bind(&torrent::seed_rank, _2, boost::ref(m_settings))); |
|
} |
|
|
|
if (settings().auto_manage_prefer_seeds) |
|
{ |
|
auto_manage_torrents(seeds, dht_limit, tracker_limit, lsd_limit |
|
, hard_limit, num_seeds); |
|
auto_manage_torrents(downloaders, dht_limit, tracker_limit, lsd_limit |
|
, hard_limit, num_downloaders); |
|
} |
|
else |
|
{ |
|
auto_manage_torrents(downloaders, dht_limit, tracker_limit, lsd_limit |
|
, hard_limit, num_downloaders); |
|
auto_manage_torrents(seeds, dht_limit, tracker_limit, lsd_limit |
|
, hard_limit, num_seeds); |
|
} |
|
} |
|
|
|
void session_impl::recalculate_optimistic_unchoke_slots() |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
if (m_allowed_upload_slots == 0) return; |
|
|
|
std::vector<policy::peer*> opt_unchoke; |
|
|
|
for (connection_map::iterator i = m_connections.begin() |
|
, end(m_connections.end()); i != end; ++i) |
|
{ |
|
peer_connection* p = i->get(); |
|
TORRENT_ASSERT(p); |
|
policy::peer* pi = p->peer_info_struct(); |
|
if (!pi) continue; |
|
if (pi->web_seed) continue; |
|
torrent* t = p->associated_torrent().lock().get(); |
|
if (!t) continue; |
|
if (t->is_paused()) continue; |
|
|
|
if (pi->optimistically_unchoked) |
|
{ |
|
TORRENT_ASSERT(!p->is_choked()); |
|
opt_unchoke.push_back(pi); |
|
} |
|
|
|
if (!p->is_connecting() |
|
&& !p->is_disconnecting() |
|
&& p->is_peer_interested() |
|
&& t->free_upload_slots() |
|
&& p->is_choked() |
|
&& !p->ignore_unchoke_slots() |
|
&& t->valid_metadata()) |
|
{ |
|
opt_unchoke.push_back(pi); |
|
} |
|
} |
|
|
|
// find the peers that has been waiting the longest to be optimistically |
|
// unchoked |
|
|
|
// avoid having a bias towards peers that happen to be sorted first |
|
std::random_shuffle(opt_unchoke.begin(), opt_unchoke.end()); |
|
|
|
// sort all candidates based on when they were last optimistically |
|
// unchoked. |
|
std::sort(opt_unchoke.begin(), opt_unchoke.end() |
|
, boost::bind(&policy::peer::last_optimistically_unchoked, _1) |
|
< boost::bind(&policy::peer::last_optimistically_unchoked, _2)); |
|
|
|
int num_opt_unchoke = m_settings.num_optimistic_unchoke_slots; |
|
if (num_opt_unchoke == 0) num_opt_unchoke = (std::max)(1, m_allowed_upload_slots / 5); |
|
|
|
// unchoke the first num_opt_unchoke peers in the candidate set |
|
// and make sure that the others are choked |
|
for (std::vector<policy::peer*>::iterator i = opt_unchoke.begin() |
|
, end(opt_unchoke.end()); i != end; ++i) |
|
{ |
|
policy::peer* pi = *i; |
|
if (num_opt_unchoke > 0) |
|
{ |
|
--num_opt_unchoke; |
|
if (!pi->optimistically_unchoked) |
|
{ |
|
torrent* t = pi->connection->associated_torrent().lock().get(); |
|
bool ret = t->unchoke_peer(*pi->connection, true); |
|
if (ret) |
|
{ |
|
pi->optimistically_unchoked = true; |
|
++m_num_unchoked; |
|
pi->last_optimistically_unchoked = session_time(); |
|
} |
|
else |
|
{ |
|
// we failed to unchoke it, increment the count again |
|
++num_opt_unchoke; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if (pi->optimistically_unchoked) |
|
{ |
|
torrent* t = pi->connection->associated_torrent().lock().get(); |
|
pi->optimistically_unchoked = false; |
|
t->choke_peer(*pi->connection); |
|
--m_num_unchoked; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void session_impl::try_connect_more_peers(int num_downloads, int num_downloads_peers) |
|
{ |
|
// let torrents connect to peers if they want to |
|
// if there are any torrents and any free slots |
|
|
|
// this loop will "hand out" max(connection_speed |
|
// , half_open.free_slots()) to the torrents, in a |
|
// round robin fashion, so that every torrent is |
|
// equally likely to connect to a peer |
|
|
|
int free_slots = m_half_open.free_slots(); |
|
int max_connections = m_settings.connection_speed; |
|
// boost connections are connections made by torrent connection |
|
// boost, which are done immediately on a tracker response. These |
|
// connections needs to be deducted from this second |
|
if (m_boost_connections > 0) |
|
{ |
|
if (m_boost_connections > max_connections) |
|
{ |
|
m_boost_connections -= max_connections; |
|
max_connections = 0; |
|
} |
|
else |
|
{ |
|
max_connections -= m_boost_connections; |
|
m_boost_connections = 0; |
|
} |
|
} |
|
|
|
// this logic is here to smooth out the number of new connection |
|
// attempts over time, to prevent connecting a large number of |
|
// sockets, wait 10 seconds, and then try again |
|
int limit = (std::min)(m_settings.connections_limit - num_connections(), free_slots); |
|
if (m_settings.smooth_connects && max_connections > (limit+1) / 2) |
|
max_connections = (limit+1) / 2; |
|
|
|
// TODO: use a lower limit than m_settings.connections_limit |
|
// to allocate the to 10% or so of connection slots for incoming |
|
// connections |
|
if (!m_torrents.empty() |
|
&& free_slots > -m_half_open.limit() |
|
&& num_connections() < m_settings.connections_limit |
|
&& !m_abort |
|
&& m_settings.connection_speed > 0 |
|
&& max_connections > 0) |
|
{ |
|
// this is the maximum number of connections we will |
|
// attempt this tick |
|
int average_peers = 0; |
|
if (num_downloads > 0) |
|
average_peers = num_downloads_peers / num_downloads; |
|
|
|
if (m_next_connect_torrent == m_torrents.end()) |
|
m_next_connect_torrent = m_torrents.begin(); |
|
|
|
int steps_since_last_connect = 0; |
|
int num_torrents = int(m_torrents.size()); |
|
for (;;) |
|
{ |
|
torrent& t = *m_next_connect_torrent->second; |
|
if (t.want_more_peers()) |
|
{ |
|
TORRENT_ASSERT(t.allows_peers()); |
|
// have a bias to give more connection attempts |
|
// to downloading torrents than seed, and even |
|
// more to downloading torrents with less than |
|
// average number of connections |
|
int num_attempts = 1; |
|
if (!t.is_finished()) |
|
{ |
|
// TODO: make this bias configurable |
|
// TODO: also take average_peers into account, to create a bias for downloading torrents with < average peers |
|
TORRENT_ASSERT(m_num_active_downloading > 0); |
|
num_attempts += m_num_active_finished / m_num_active_downloading; |
|
} |
|
while (m_current_connect_attempts < num_attempts) |
|
{ |
|
TORRENT_TRY |
|
{ |
|
++m_current_connect_attempts; |
|
if (t.try_connect_peer()) |
|
{ |
|
--max_connections; |
|
--free_slots; |
|
steps_since_last_connect = 0; |
|
#ifdef TORRENT_STATS |
|
++m_connection_attempts; |
|
#endif |
|
} |
|
} |
|
TORRENT_CATCH(std::bad_alloc&) |
|
{ |
|
// we ran out of memory trying to connect to a peer |
|
// lower the global limit to the number of peers |
|
// we already have |
|
m_settings.connections_limit = num_connections(); |
|
if (m_settings.connections_limit < 2) m_settings.connections_limit = 2; |
|
} |
|
if (!t.want_more_peers()) break; |
|
if (free_slots <= -m_half_open.limit()) return; |
|
if (max_connections == 0) return; |
|
if (num_connections() >= m_settings.connections_limit) return; |
|
} |
|
} |
|
|
|
++m_next_connect_torrent; |
|
m_current_connect_attempts = 0; |
|
++steps_since_last_connect; |
|
if (m_next_connect_torrent == m_torrents.end()) |
|
m_next_connect_torrent = m_torrents.begin(); |
|
|
|
// if we have gone a whole loop without |
|
// handing out a single connection, break |
|
if (steps_since_last_connect > num_torrents + 1) break; |
|
// if there are no more free connection slots, abort |
|
if (free_slots <= -m_half_open.limit()) break; |
|
// if we should not make any more connections |
|
// attempts this tick, abort |
|
if (max_connections == 0) break; |
|
// maintain the global limit on number of connections |
|
if (num_connections() >= m_settings.connections_limit) break; |
|
} |
|
} |
|
} |
|
|
|
void session_impl::recalculate_unchoke_slots(int congested_torrents |
|
, int uncongested_torrents) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
INVARIANT_CHECK; |
|
|
|
ptime now = time_now(); |
|
time_duration unchoke_interval = now - m_last_choke; |
|
m_last_choke = now; |
|
|
|
// build list of all peers that are |
|
// unchokable. |
|
std::vector<peer_connection*> peers; |
|
for (connection_map::iterator i = m_connections.begin(); |
|
i != m_connections.end();) |
|
{ |
|
boost::intrusive_ptr<peer_connection> p = *i; |
|
TORRENT_ASSERT(p); |
|
++i; |
|
torrent* t = p->associated_torrent().lock().get(); |
|
policy::peer* pi = p->peer_info_struct(); |
|
|
|
if (p->ignore_unchoke_slots() || t == 0 || pi == 0 || pi->web_seed || t->is_paused()) |
|
continue; |
|
|
|
if (m_settings.choking_algorithm == session_settings::bittyrant_choker) |
|
{ |
|
if (!p->is_choked() && p->is_interesting()) |
|
{ |
|
if (!p->has_peer_choked()) |
|
{ |
|
// we're unchoked, we may want to lower our estimated |
|
// reciprocation rate |
|
p->decrease_est_reciprocation_rate(); |
|
} |
|
else |
|
{ |
|
// we've unchoked this peer, and it hasn't reciprocated |
|
// we may want to increase our estimated reciprocation rate |
|
p->increase_est_reciprocation_rate(); |
|
} |
|
} |
|
} |
|
|
|
if (!p->is_peer_interested() |
|
|| p->is_disconnecting() |
|
|| p->is_connecting() |
|
|| (p->share_diff() < -free_upload_amount |
|
&& !t->is_seed())) |
|
{ |
|
// this peer is not unchokable. So, if it's unchoked |
|
// already, make sure to choke it. |
|
if (p->is_choked()) continue; |
|
if (pi && pi->optimistically_unchoked) |
|
{ |
|
pi->optimistically_unchoked = false; |
|
// force a new optimistic unchoke |
|
m_optimistic_unchoke_time_scaler = 0; |
|
} |
|
t->choke_peer(*p); |
|
continue; |
|
} |
|
peers.push_back(p.get()); |
|
} |
|
|
|
if (m_settings.choking_algorithm == session_settings::rate_based_choker) |
|
{ |
|
m_allowed_upload_slots = 0; |
|
std::sort(peers.begin(), peers.end() |
|
, boost::bind(&peer_connection::upload_rate_compare, _1, _2)); |
|
|
|
#ifdef TORRENT_DEBUG |
|
for (std::vector<peer_connection*>::const_iterator i = peers.begin() |
|
, end(peers.end()), prev(peers.end()); i != end; ++i) |
|
{ |
|
if (prev != end) |
|
{ |
|
boost::shared_ptr<torrent> t1 = (*prev)->associated_torrent().lock(); |
|
TORRENT_ASSERT(t1); |
|
boost::shared_ptr<torrent> t2 = (*i)->associated_torrent().lock(); |
|
TORRENT_ASSERT(t2); |
|
TORRENT_ASSERT((*prev)->uploaded_in_last_round() * 1000 |
|
* (1 + t1->priority()) / total_milliseconds(unchoke_interval) |
|
>= (*i)->uploaded_in_last_round() * 1000 |
|
* (1 + t2->priority()) / total_milliseconds(unchoke_interval)); |
|
} |
|
prev = i; |
|
} |
|
#endif |
|
|
|
// TODO: make configurable |
|
int rate_threshold = 1024; |
|
|
|
for (std::vector<peer_connection*>::const_iterator i = peers.begin() |
|
, end(peers.end()); i != end; ++i) |
|
{ |
|
peer_connection const& p = **i; |
|
int rate = int(p.uploaded_in_last_round() |
|
* 1000 / total_milliseconds(unchoke_interval)); |
|
|
|
if (rate < rate_threshold) break; |
|
|
|
++m_allowed_upload_slots; |
|
|
|
// TODO: make configurable |
|
rate_threshold += 1024; |
|
} |
|
// allow one optimistic unchoke |
|
++m_allowed_upload_slots; |
|
} |
|
|
|
if (m_settings.choking_algorithm == session_settings::bittyrant_choker) |
|
{ |
|
// if we're using the bittyrant choker, sort peers by their return |
|
// on investment. i.e. download rate / upload rate |
|
std::sort(peers.begin(), peers.end() |
|
, boost::bind(&peer_connection::bittyrant_unchoke_compare, _1, _2)); |
|
} |
|
else |
|
{ |
|
// sorts the peers that are eligible for unchoke by download rate and secondary |
|
// by total upload. The reason for this is, if all torrents are being seeded, |
|
// the download rate will be 0, and the peers we have sent the least to should |
|
// be unchoked |
|
std::sort(peers.begin(), peers.end() |
|
, boost::bind(&peer_connection::unchoke_compare, _1, _2)); |
|
} |
|
|
|
// auto unchoke |
|
int upload_limit = m_bandwidth_channel[peer_connection::upload_channel]->throttle(); |
|
if (m_settings.choking_algorithm == session_settings::auto_expand_choker |
|
&& upload_limit > 0) |
|
{ |
|
// if our current upload rate is less than 90% of our |
|
// limit AND most torrents are not "congested", i.e. |
|
// they are not holding back because of a per-torrent |
|
// limit |
|
if (m_stat.upload_rate() < upload_limit * 0.9f |
|
&& m_allowed_upload_slots <= m_num_unchoked + 1 |
|
&& congested_torrents < uncongested_torrents |
|
&& m_upload_rate.queue_size() < 2) |
|
{ |
|
++m_allowed_upload_slots; |
|
} |
|
else if (m_upload_rate.queue_size() > 1 |
|
&& m_allowed_upload_slots > m_settings.unchoke_slots_limit |
|
&& m_settings.unchoke_slots_limit >= 0) |
|
{ |
|
--m_allowed_upload_slots; |
|
} |
|
} |
|
|
|
int num_opt_unchoke = m_settings.num_optimistic_unchoke_slots; |
|
if (num_opt_unchoke == 0) num_opt_unchoke = (std::max)(1, m_allowed_upload_slots / 5); |
|
|
|
// reserve some upload slots for optimistic unchokes |
|
int unchoke_set_size = m_allowed_upload_slots - num_opt_unchoke; |
|
|
|
int upload_capacity_left = 0; |
|
if (m_settings.choking_algorithm == session_settings::bittyrant_choker) |
|
{ |
|
upload_capacity_left = m_upload_channel.throttle(); |
|
if (upload_capacity_left == 0) |
|
{ |
|
// we don't know at what rate we can upload. If we have a |
|
// measurement of the peak, use that + 10kB/s, otherwise |
|
// assume 20 kB/s |
|
upload_capacity_left = (std::max)(20000, m_peak_up_rate + 10000); |
|
if (m_alerts.should_post<performance_alert>()) |
|
m_alerts.post_alert(performance_alert(torrent_handle() |
|
, performance_alert::bittyrant_with_no_uplimit)); |
|
} |
|
} |
|
|
|
m_num_unchoked = 0; |
|
// go through all the peers and unchoke the first ones and choke |
|
// all the other ones. |
|
for (std::vector<peer_connection*>::iterator i = peers.begin() |
|
, end(peers.end()); i != end; ++i) |
|
{ |
|
peer_connection* p = *i; |
|
TORRENT_ASSERT(p); |
|
TORRENT_ASSERT(!p->ignore_unchoke_slots()); |
|
|
|
// this will update the m_uploaded_at_last_unchoke |
|
// #error this should be called for all peers! |
|
p->reset_choke_counters(); |
|
|
|
torrent* t = p->associated_torrent().lock().get(); |
|
TORRENT_ASSERT(t); |
|
|
|
// if this peer should be unchoked depends on different things |
|
// in different unchoked schemes |
|
bool unchoke = false; |
|
if (m_settings.choking_algorithm == session_settings::bittyrant_choker) |
|
{ |
|
unchoke = p->est_reciprocation_rate() <= upload_capacity_left; |
|
} |
|
else |
|
{ |
|
unchoke = unchoke_set_size > 0; |
|
} |
|
|
|
if (unchoke) |
|
{ |
|
upload_capacity_left -= p->est_reciprocation_rate(); |
|
|
|
// yes, this peer should be unchoked |
|
if (p->is_choked()) |
|
{ |
|
if (!t->unchoke_peer(*p)) |
|
continue; |
|
} |
|
|
|
--unchoke_set_size; |
|
++m_num_unchoked; |
|
|
|
TORRENT_ASSERT(p->peer_info_struct()); |
|
if (p->peer_info_struct()->optimistically_unchoked) |
|
{ |
|
// force a new optimistic unchoke |
|
// since this one just got promoted into the |
|
// proper unchoke set |
|
m_optimistic_unchoke_time_scaler = 0; |
|
p->peer_info_struct()->optimistically_unchoked = false; |
|
} |
|
} |
|
else |
|
{ |
|
// no, this peer should be shoked |
|
TORRENT_ASSERT(p->peer_info_struct()); |
|
if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) |
|
t->choke_peer(*p); |
|
if (!p->is_choked()) |
|
++m_num_unchoked; |
|
} |
|
} |
|
} |
|
|
|
#if defined _MSC_VER && defined TORRENT_DEBUG |
|
static void straight_to_debugger(unsigned int, _EXCEPTION_POINTERS*) |
|
{ throw; } |
|
#endif |
|
|
|
void session_impl::main_thread() |
|
{ |
|
#if defined _MSC_VER && defined TORRENT_DEBUG |
|
// workaround for microsofts |
|
// hardware exceptions that makes |
|
// it hard to debug stuff |
|
::_set_se_translator(straight_to_debugger); |
|
#endif |
|
#if (defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS) && defined BOOST_HAS_PTHREADS |
|
m_network_thread = pthread_self(); |
|
#endif |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
// initialize async operations |
|
init(); |
|
|
|
bool stop_loop = false; |
|
while (!stop_loop) |
|
{ |
|
error_code ec; |
|
m_io_service.run(ec); |
|
if (ec) |
|
{ |
|
#ifdef TORRENT_DEBUG |
|
fprintf(stderr, "%s\n", ec.message().c_str()); |
|
std::string err = ec.message(); |
|
#endif |
|
TORRENT_ASSERT(false); |
|
} |
|
m_io_service.reset(); |
|
|
|
stop_loop = m_abort; |
|
} |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" locking mutex"); |
|
#endif |
|
|
|
/* |
|
#ifdef TORRENT_DEBUG |
|
for (torrent_map::iterator i = m_torrents.begin(); |
|
i != m_torrents.end(); ++i) |
|
{ |
|
TORRENT_ASSERT(i->second->num_peers() == 0); |
|
} |
|
#endif |
|
*/ |
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log(" cleaning up torrents"); |
|
#endif |
|
m_torrents.clear(); |
|
|
|
TORRENT_ASSERT(m_torrents.empty()); |
|
TORRENT_ASSERT(m_connections.empty()); |
|
|
|
#if (defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS) && defined BOOST_HAS_PTHREADS |
|
m_network_thread = 0; |
|
#endif |
|
} |
|
|
|
|
|
// the return value from this function is valid only as long as the |
|
// session is locked! |
|
boost::weak_ptr<torrent> session_impl::find_torrent(sha1_hash const& info_hash) const |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
torrent_map::const_iterator i = m_torrents.find(info_hash); |
|
#ifdef TORRENT_DEBUG |
|
for (torrent_map::const_iterator j |
|
= m_torrents.begin(); j != m_torrents.end(); ++j) |
|
{ |
|
torrent* p = boost::get_pointer(j->second); |
|
TORRENT_ASSERT(p); |
|
} |
|
#endif |
|
if (i != m_torrents.end()) return i->second; |
|
return boost::weak_ptr<torrent>(); |
|
} |
|
|
|
boost::weak_ptr<torrent> session_impl::find_torrent(std::string const& uuid) const |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
std::map<std::string, boost::shared_ptr<torrent> >::const_iterator i |
|
= m_uuids.find(uuid); |
|
if (i != m_uuids.end()) return i->second; |
|
return boost::weak_ptr<torrent>(); |
|
} |
|
|
|
// returns true if lhs is a better disconnect candidate than rhs |
|
bool compare_disconnect_torrent(session_impl::torrent_map::value_type const& lhs |
|
, session_impl::torrent_map::value_type const& rhs) |
|
{ |
|
// a torrent with 0 peers is never a good disconnect candidate |
|
// since there's nothing to disconnect |
|
if ((lhs.second->num_peers() == 0) != (rhs.second->num_peers() == 0)) |
|
return lhs.second->num_peers() != 0; |
|
|
|
// other than that, always prefer to disconnect peers from seeding torrents |
|
// in order to not harm downloading ones |
|
if (lhs.second->is_seed() != rhs.second->is_seed()) |
|
return lhs.second->is_seed(); |
|
|
|
return lhs.second->num_peers() > rhs.second->num_peers(); |
|
} |
|
|
|
boost::weak_ptr<torrent> session_impl::find_disconnect_candidate_torrent() const |
|
{ |
|
aux::session_impl::torrent_map::const_iterator i = std::min_element(m_torrents.begin(), m_torrents.end() |
|
, boost::bind(&compare_disconnect_torrent, _1, _2)); |
|
|
|
TORRENT_ASSERT(i != m_torrents.end()); |
|
if (i == m_torrents.end()) return boost::shared_ptr<torrent>(); |
|
|
|
return i->second; |
|
} |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
boost::shared_ptr<logger> session_impl::create_log(std::string const& name |
|
, int instance, bool append) |
|
{ |
|
error_code ec; |
|
// current options are file_logger, cout_logger and null_logger |
|
return boost::shared_ptr<logger>(new logger(m_logpath, name, instance, append)); |
|
} |
|
|
|
void session_impl::session_log(char const* fmt, ...) const |
|
{ |
|
if (!m_logger) return; |
|
|
|
va_list v; |
|
va_start(v, fmt); |
|
|
|
char usr[400]; |
|
vsnprintf(usr, sizeof(usr), fmt, v); |
|
va_end(v); |
|
char buf[450]; |
|
snprintf(buf, sizeof(buf), "%s: %s\n", time_now_string(), usr); |
|
// printf is the bitcoin/util.h logger |
|
printf("%s", buf); |
|
//(*m_logger) << buf; |
|
} |
|
#endif |
|
|
|
void session_impl::get_torrent_status(std::vector<torrent_status>* ret |
|
, boost::function<bool(torrent_status const&)> const& pred |
|
, boost::uint32_t flags) const |
|
{ |
|
for (torrent_map::const_iterator i |
|
= m_torrents.begin(), end(m_torrents.end()); |
|
i != end; ++i) |
|
{ |
|
if (i->second->is_aborted()) continue; |
|
torrent_status st; |
|
i->second->status(&st, flags); |
|
if (!pred(st)) continue; |
|
ret->push_back(st); |
|
} |
|
} |
|
|
|
void session_impl::refresh_torrent_status(std::vector<torrent_status>* ret |
|
, boost::uint32_t flags) const |
|
{ |
|
for (std::vector<torrent_status>::iterator i |
|
= ret->begin(), end(ret->end()); i != end; ++i) |
|
{ |
|
boost::shared_ptr<torrent> t = i->handle.m_torrent.lock(); |
|
if (!t) continue; |
|
t->status(&*i, flags); |
|
} |
|
} |
|
|
|
void session_impl::post_torrent_updates() |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
std::auto_ptr<state_update_alert> alert(new state_update_alert()); |
|
alert->status.reserve(m_state_updates.size()); |
|
|
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS |
|
m_posting_torrent_updates = true; |
|
#endif |
|
|
|
for (std::vector<boost::weak_ptr<torrent> >::iterator i = m_state_updates.begin() |
|
, end(m_state_updates.end()); i != end; ++i) |
|
{ |
|
boost::shared_ptr<torrent> t = i->lock(); |
|
if (!t) continue; |
|
alert->status.push_back(torrent_status()); |
|
t->status(&alert->status.back(), 0xffffffff); |
|
t->clear_in_state_update(); |
|
} |
|
m_state_updates.clear(); |
|
|
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS |
|
m_posting_torrent_updates = false; |
|
#endif |
|
|
|
m_alerts.post_alert_ptr(alert.release()); |
|
} |
|
|
|
std::vector<torrent_handle> session_impl::get_torrents() const |
|
{ |
|
std::vector<torrent_handle> ret; |
|
|
|
for (torrent_map::const_iterator i |
|
= m_torrents.begin(), end(m_torrents.end()); |
|
i != end; ++i) |
|
{ |
|
if (i->second->is_aborted()) continue; |
|
ret.push_back(torrent_handle(i->second)); |
|
} |
|
return ret; |
|
} |
|
|
|
torrent_handle session_impl::find_torrent_handle(sha1_hash const& info_hash) |
|
{ |
|
return torrent_handle(find_torrent(info_hash)); |
|
} |
|
|
|
void session_impl::async_add_torrent(add_torrent_params* params) |
|
{ |
|
error_code ec; |
|
torrent_handle handle = add_torrent(*params, ec); |
|
delete params; |
|
} |
|
|
|
torrent_handle session_impl::add_torrent(add_torrent_params const& p |
|
, error_code& ec) |
|
{ |
|
torrent_handle h = add_torrent_impl(p, ec); |
|
m_alerts.post_alert(add_torrent_alert(h, p, ec)); |
|
return h; |
|
} |
|
|
|
torrent_handle session_impl::add_torrent_impl(add_torrent_params const& p |
|
, error_code& ec) |
|
{ |
|
TORRENT_ASSERT(!p.save_path.empty()); |
|
|
|
#ifndef TORRENT_NO_DEPRECATE |
|
p.update_flags(); |
|
#endif |
|
|
|
add_torrent_params params = p; |
|
if (string_begins_no_case("magnet:", params.url.c_str())) |
|
{ |
|
parse_magnet_uri(params.url, params, ec); |
|
if (ec) return torrent_handle(); |
|
params.url.clear(); |
|
} |
|
|
|
if (params.ti && params.ti->is_valid() && params.ti->num_files() == 0) |
|
{ |
|
ec = errors::no_files_in_torrent; |
|
return torrent_handle(); |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
// add p.dht_nodes to the DHT, if enabled |
|
if (m_dht && !p.dht_nodes.empty()) |
|
{ |
|
for (std::vector<std::pair<std::string, int> >::const_iterator i = p.dht_nodes.begin() |
|
, end(p.dht_nodes.end()); i != end; ++i) |
|
m_dht->add_node(*i); |
|
} |
|
#endif |
|
|
|
// INVARIANT_CHECK; |
|
|
|
if (is_aborted()) |
|
{ |
|
ec = errors::session_is_closing; |
|
return torrent_handle(); |
|
} |
|
|
|
// figure out the info hash of the torrent |
|
sha1_hash const* ih = 0; |
|
sha1_hash tmp; |
|
if (params.ti) ih = ¶ms.ti->info_hash(); |
|
else if (!params.url.empty()) |
|
{ |
|
// in order to avoid info-hash collisions, for |
|
// torrents where we don't have an info-hash, but |
|
// just a URL, set the temporary info-hash to the |
|
// hash of the URL. This will be changed once we |
|
// have the actual .torrent file |
|
tmp = hasher(¶ms.url[0], params.url.size()).final(); |
|
ih = &tmp; |
|
} |
|
else ih = ¶ms.info_hash; |
|
|
|
// we don't have a torrent file. If the user provided |
|
// resume data, there may be some metadata in there |
|
if ((!params.ti || !params.ti->is_valid()) |
|
&& !params.resume_data.empty()) |
|
{ |
|
int pos; |
|
error_code ec; |
|
lazy_entry tmp; |
|
lazy_entry const* info = 0; |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
session_log("adding magnet link with resume data"); |
|
#endif |
|
if (lazy_bdecode(¶ms.resume_data[0], ¶ms.resume_data[0] |
|
+ params.resume_data.size(), tmp, ec, &pos) == 0 |
|
&& tmp.type() == lazy_entry::dict_t |
|
/* && (info = tmp.dict_find_dict("info")) */) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
session_log("found metadata in resume data"); |
|
#endif |
|
// verify the info-hash of the metadata stored in the resume file matches |
|
// the torrent we're loading |
|
|
|
/* [MF] |
|
std::pair<char const*, int> buf = info->data_section(); |
|
sha1_hash resume_ih = hasher(buf.first, buf.second).final(); |
|
*/ |
|
std::string info_hash = tmp.dict_find_string_value("info-hash"); |
|
sha1_hash resume_ih = sha1_hash(info_hash); |
|
|
|
// if url is set, the info_hash is not actually the info-hash of the |
|
// torrent, but the hash of the URL, until we have the full torrent |
|
// only require the info-hash to match if we actually passed in one |
|
if (resume_ih == params.info_hash |
|
|| !params.url.empty() |
|
|| params.info_hash.is_all_zeros()) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
session_log("info-hash matched"); |
|
#endif |
|
params.ti = new torrent_info(resume_ih, params.name); |
|
/* |
|
if (params.ti->parse_info_section(*info, ec, 0)) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING |
|
session_log("successfully loaded metadata from resume file"); |
|
#endif |
|
// make the info-hash be the one in the resume file |
|
params.info_hash = resume_ih; |
|
ih = ¶ms.info_hash; |
|
} |
|
else |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("failed to load metadata from resume file: %s" |
|
, ec.message().c_str()); |
|
#endif |
|
} |
|
*/ |
|
} |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
else |
|
{ |
|
session_log("metadata info-hash failed"); |
|
} |
|
#endif |
|
} |
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
else |
|
{ |
|
session_log("no metadata found"); |
|
} |
|
#endif |
|
} |
|
|
|
// is the torrent already active? |
|
boost::shared_ptr<torrent> torrent_ptr = find_torrent(*ih).lock(); |
|
if (!torrent_ptr && !params.uuid.empty()) torrent_ptr = find_torrent(params.uuid).lock(); |
|
// TODO: 2 if we still can't find the torrent, we should probably look for it by url here |
|
|
|
if (torrent_ptr) |
|
{ |
|
if ((params.flags & add_torrent_params::flag_duplicate_is_error) == 0) |
|
{ |
|
if (!params.uuid.empty() && torrent_ptr->uuid().empty()) |
|
torrent_ptr->set_uuid(params.uuid); |
|
if (!params.url.empty() && torrent_ptr->url().empty()) |
|
torrent_ptr->set_url(params.url); |
|
if (!params.source_feed_url.empty() && torrent_ptr->source_feed_url().empty()) |
|
torrent_ptr->set_source_feed_url(params.source_feed_url); |
|
return torrent_handle(torrent_ptr); |
|
} |
|
|
|
ec = errors::duplicate_torrent; |
|
return torrent_handle(); |
|
} |
|
|
|
int queue_pos = 0; |
|
for (torrent_map::const_iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
int pos = i->second->queue_position(); |
|
if (pos >= queue_pos) queue_pos = pos + 1; |
|
} |
|
|
|
torrent_ptr.reset(new torrent(*this, m_listen_interface |
|
, 16 * 1024, queue_pos, params, *ih)); |
|
torrent_ptr->start(); |
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS |
|
for (ses_extension_list_t::iterator i = m_ses_extensions.begin() |
|
, end(m_ses_extensions.end()); i != end; ++i) |
|
{ |
|
boost::shared_ptr<torrent_plugin> tp((*i)->new_torrent(torrent_ptr.get(), params.userdata)); |
|
if (tp) torrent_ptr->add_extension(tp); |
|
} |
|
#endif |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_dht && params.ti) |
|
{ |
|
torrent_info::nodes_t const& nodes = params.ti->nodes(); |
|
std::for_each(nodes.begin(), nodes.end(), boost::bind( |
|
(void(dht::dht_tracker::*)(std::pair<std::string, int> const&)) |
|
&dht::dht_tracker::add_node |
|
, boost::ref(m_dht), _1)); |
|
} |
|
#endif |
|
|
|
m_torrents.insert(std::make_pair(*ih, torrent_ptr)); |
|
if (!params.uuid.empty() || !params.url.empty()) |
|
m_uuids.insert(std::make_pair(params.uuid.empty() |
|
? params.url : params.uuid, torrent_ptr)); |
|
|
|
if (m_alerts.should_post<torrent_added_alert>()) |
|
m_alerts.post_alert(torrent_added_alert(torrent_ptr->get_handle())); |
|
|
|
// recalculate auto-managed torrents sooner (or put it off) |
|
// if another torrent will be added within one second from now |
|
// we want to put it off again anyway. So that while we're adding |
|
// a boat load of torrents, we postpone the recalculation until |
|
// we're done adding them all (since it's kind of an expensive operation) |
|
if (params.flags & add_torrent_params::flag_auto_managed) |
|
trigger_auto_manage(); |
|
|
|
return torrent_handle(torrent_ptr); |
|
} |
|
|
|
void session_impl::queue_check_torrent(boost::shared_ptr<torrent> const& t) |
|
{ |
|
if (m_abort) return; |
|
TORRENT_ASSERT(t->should_check_files()); |
|
TORRENT_ASSERT(t->state() != torrent_status::checking_files); |
|
if (m_queued_for_checking.empty()) t->start_checking(); |
|
else t->set_state(torrent_status::queued_for_checking); |
|
TORRENT_ASSERT(std::find(m_queued_for_checking.begin() |
|
, m_queued_for_checking.end(), t) == m_queued_for_checking.end()); |
|
m_queued_for_checking.push_back(t); |
|
} |
|
|
|
void session_impl::dequeue_check_torrent(boost::shared_ptr<torrent> const& t) |
|
{ |
|
INVARIANT_CHECK; |
|
TORRENT_ASSERT(t->state() == torrent_status::checking_files |
|
|| t->state() == torrent_status::queued_for_checking); |
|
|
|
if (m_queued_for_checking.empty()) return; |
|
|
|
boost::shared_ptr<torrent> next_check = *m_queued_for_checking.begin(); |
|
check_queue_t::iterator done = m_queued_for_checking.end(); |
|
for (check_queue_t::iterator i = m_queued_for_checking.begin() |
|
, end(m_queued_for_checking.end()); i != end; ++i) |
|
{ |
|
// the reason m_paused is in there is because when the session |
|
// is paused, all torrents that are queued ar all of a sudden |
|
// not supposed to be queued anymore. The first torrent that gets |
|
// removed from the queue will hence trigger this assert, without |
|
// the m_paused exception |
|
TORRENT_ASSERT(*i == t || (*i)->should_check_files() || m_paused); |
|
if (*i == t) done = i; |
|
else if (next_check == t || next_check->queue_position() > (*i)->queue_position()) |
|
next_check = *i; |
|
} |
|
TORRENT_ASSERT(next_check != t || m_queued_for_checking.size() == 1); |
|
// only start a new one if we removed the one that is checking |
|
TORRENT_ASSERT(done != m_queued_for_checking.end()); |
|
if (done == m_queued_for_checking.end()) return; |
|
|
|
if (next_check != t |
|
&& t->state() == torrent_status::checking_files |
|
&& !m_paused) |
|
{ |
|
next_check->start_checking(); |
|
} |
|
|
|
m_queued_for_checking.erase(done); |
|
} |
|
|
|
void session_impl::remove_torrent(const torrent_handle& h, int options) |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
boost::shared_ptr<torrent> tptr = h.m_torrent.lock(); |
|
if (!tptr) return; |
|
|
|
remove_torrent_impl(tptr, options); |
|
|
|
if (m_alerts.should_post<torrent_removed_alert>()) |
|
m_alerts.post_alert(torrent_removed_alert(tptr->get_handle(), tptr->info_hash())); |
|
|
|
tptr->abort(); |
|
tptr->set_queue_position(-1); |
|
} |
|
|
|
void session_impl::remove_torrent_impl(boost::shared_ptr<torrent> tptr, int options) |
|
{ |
|
// remove from uuid list |
|
if (!tptr->uuid().empty()) |
|
{ |
|
std::map<std::string, boost::shared_ptr<torrent> >::iterator j |
|
= m_uuids.find(tptr->uuid()); |
|
if (j != m_uuids.end()) m_uuids.erase(j); |
|
} |
|
|
|
torrent_map::iterator i = |
|
m_torrents.find(tptr->torrent_file().info_hash()); |
|
|
|
// this torrent might be filed under the URL-hash |
|
if (i == m_torrents.end() && !tptr->url().empty()) |
|
{ |
|
std::string const& url = tptr->url(); |
|
sha1_hash urlhash = hasher(&url[0], url.size()).final(); |
|
i = m_torrents.find(urlhash); |
|
} |
|
|
|
if (i == m_torrents.end()) return; |
|
|
|
torrent& t = *i->second; |
|
if (options & session::delete_files) |
|
{ |
|
if (!t.delete_files()) |
|
{ |
|
if (m_alerts.should_post<torrent_delete_failed_alert>()) |
|
m_alerts.post_alert(torrent_delete_failed_alert(t.get_handle(), error_code())); |
|
} |
|
} |
|
|
|
tptr->update_guage(); |
|
|
|
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS |
|
sha1_hash i_hash = t.torrent_file().info_hash(); |
|
#endif |
|
#ifndef TORRENT_DISABLE_DHT |
|
if (i == m_next_dht_torrent) |
|
++m_next_dht_torrent; |
|
#endif |
|
if (i == m_next_lsd_torrent) |
|
++m_next_lsd_torrent; |
|
if (i == m_next_connect_torrent) |
|
++m_next_connect_torrent; |
|
|
|
m_torrents.erase(i); |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_next_dht_torrent == m_torrents.end()) |
|
m_next_dht_torrent = m_torrents.begin(); |
|
#endif |
|
if (m_next_lsd_torrent == m_torrents.end()) |
|
m_next_lsd_torrent = m_torrents.begin(); |
|
if (m_next_connect_torrent == m_torrents.end()) |
|
m_next_connect_torrent = m_torrents.begin(); |
|
|
|
std::list<boost::shared_ptr<torrent> >::iterator k |
|
= std::find(m_queued_for_checking.begin(), m_queued_for_checking.end(), tptr); |
|
if (k != m_queued_for_checking.end()) m_queued_for_checking.erase(k); |
|
TORRENT_ASSERT(m_torrents.find(i_hash) == m_torrents.end()); |
|
} |
|
|
|
void session_impl::listen_on( |
|
std::pair<int, int> const& port_range |
|
, error_code& ec |
|
, const char* net_interface, int flags) |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
tcp::endpoint new_interface; |
|
if (net_interface && std::strlen(net_interface) > 0) |
|
{ |
|
new_interface = tcp::endpoint(address::from_string(net_interface, ec), port_range.first); |
|
if (ec) |
|
{ |
|
if (m_alerts.should_post<listen_failed_alert>()) |
|
m_alerts.post_alert(listen_failed_alert(new_interface, listen_failed_alert::parse_addr, ec)); |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
session_log("listen_on: %s failed: %s" |
|
, net_interface, ec.message().c_str()); |
|
#endif |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
new_interface = tcp::endpoint(address_v4::any(), port_range.first); |
|
} |
|
|
|
m_listen_port_retries = port_range.second - port_range.first; |
|
|
|
// if the interface is the same and the socket is open |
|
// don't do anything |
|
if (new_interface == m_listen_interface |
|
&& !m_listen_sockets.empty()) |
|
return; |
|
|
|
m_listen_interface = new_interface; |
|
|
|
open_listen_port(flags, ec); |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
m_logger = create_log("main_session", listen_port(), false); |
|
session_log("session_impl::listen_on log created"); |
|
#endif |
|
} |
|
|
|
address session_impl::listen_address() const |
|
{ |
|
for (std::list<listen_socket_t>::const_iterator i = m_listen_sockets.begin() |
|
, end(m_listen_sockets.end()); i != end; ++i) |
|
{ |
|
if (i->external_address != address()) return i->external_address; |
|
} |
|
return address(); |
|
} |
|
|
|
boost::uint16_t session_impl::listen_port() const |
|
{ |
|
// if peer connections are set up to be received over a socks |
|
// proxy, and it's the same one as we're using for the tracker |
|
// just tell the tracker the socks5 port we're listening on |
|
if (m_socks_listen_socket && m_socks_listen_socket->is_open()) |
|
return m_socks_listen_port; |
|
|
|
// if not, don't tell the tracker anything if we're in force_proxy |
|
// mode. We don't want to leak our listen port since it can |
|
// potentially identify us if it is leaked elsewere |
|
if (m_settings.force_proxy) return 0; |
|
if (m_listen_sockets.empty()) return 0; |
|
return m_listen_sockets.front().external_port; |
|
} |
|
|
|
boost::uint16_t session_impl::ssl_listen_port() const |
|
{ |
|
#ifdef TORRENT_USE_OPENSSL |
|
// if peer connections are set up to be received over a socks |
|
// proxy, and it's the same one as we're using for the tracker |
|
// just tell the tracker the socks5 port we're listening on |
|
if (m_socks_listen_socket && m_socks_listen_socket->is_open() |
|
&& m_proxy.hostname == m_proxy.hostname) |
|
return m_socks_listen_port; |
|
|
|
// if not, don't tell the tracker anything if we're in force_proxy |
|
// mode. We don't want to leak our listen port since it can |
|
// potentially identify us if it is leaked elsewere |
|
if (m_settings.force_proxy) return 0; |
|
if (m_listen_sockets.empty()) return 0; |
|
for (std::list<listen_socket_t>::const_iterator i = m_listen_sockets.begin() |
|
, end(m_listen_sockets.end()); i != end; ++i) |
|
{ |
|
if (i->ssl) return i->external_port; |
|
} |
|
#endif |
|
return 0; |
|
} |
|
|
|
void session_impl::announce_lsd(sha1_hash const& ih, int port, bool broadcast) |
|
{ |
|
// use internal listen port for local peers |
|
if (m_lsd.get()) |
|
m_lsd->announce(ih, port, broadcast); |
|
} |
|
|
|
void session_impl::on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih) |
|
{ |
|
#ifdef TORRENT_STATS |
|
++m_num_messages[on_lsd_peer_counter]; |
|
#endif |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
INVARIANT_CHECK; |
|
|
|
boost::shared_ptr<torrent> t = find_torrent(ih).lock(); |
|
if (!t) return; |
|
// don't add peers from lsd to private torrents |
|
if (t->torrent_file().priv() || (t->torrent_file().is_i2p() |
|
&& !m_settings.allow_i2p_mixed)) return; |
|
|
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) |
|
session_log("added peer from local discovery: %s", print_endpoint(peer).c_str()); |
|
#endif |
|
t->get_policy().add_peer(peer, peer_id(0), peer_info::lsd, 0); |
|
if (m_alerts.should_post<lsd_peer_alert>()) |
|
m_alerts.post_alert(lsd_peer_alert(t->get_handle(), peer)); |
|
} |
|
|
|
void session_impl::on_port_map_log( |
|
char const* msg, int map_transport) |
|
{ |
|
TORRENT_ASSERT(map_transport >= 0 && map_transport <= 1); |
|
// log message |
|
#ifdef TORRENT_UPNP_LOGGING |
|
char const* transport_names[] = {"NAT-PMP", "UPnP"}; |
|
m_upnp_log << time_now_string() << " " |
|
<< transport_names[map_transport] << ": " << msg; |
|
#endif |
|
if (m_alerts.should_post<portmap_log_alert>()) |
|
m_alerts.post_alert(portmap_log_alert(map_transport, msg)); |
|
} |
|
|
|
void session_impl::on_port_mapping(int mapping, address const& ip, int port |
|
, error_code const& ec, int map_transport) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
TORRENT_ASSERT(map_transport >= 0 && map_transport <= 1); |
|
|
|
if (mapping == m_udp_mapping[map_transport] && port != 0) |
|
{ |
|
m_external_udp_port = port; |
|
if (m_alerts.should_post<portmap_alert>()) |
|
m_alerts.post_alert(portmap_alert(mapping, port |
|
, map_transport)); |
|
return; |
|
} |
|
|
|
if (mapping == m_tcp_mapping[map_transport] && port != 0) |
|
{ |
|
if (ip != address()) |
|
{ |
|
// TODO: 1 report the proper address of the router as the source IP of |
|
// this understanding of our external address, instead of the empty address |
|
set_external_address(ip, source_router, address()); |
|
} |
|
|
|
if (!m_listen_sockets.empty()) { |
|
m_listen_sockets.front().external_address = ip; |
|
m_listen_sockets.front().external_port = port; |
|
} |
|
if (m_alerts.should_post<portmap_alert>()) |
|
m_alerts.post_alert(portmap_alert(mapping, port |
|
, map_transport)); |
|
return; |
|
} |
|
|
|
if (ec) |
|
{ |
|
if (m_alerts.should_post<portmap_error_alert>()) |
|
m_alerts.post_alert(portmap_error_alert(mapping |
|
, map_transport, ec)); |
|
} |
|
else |
|
{ |
|
if (m_alerts.should_post<portmap_alert>()) |
|
m_alerts.post_alert(portmap_alert(mapping, port |
|
, map_transport)); |
|
} |
|
} |
|
|
|
session_status session_impl::status() const |
|
{ |
|
// INVARIANT_CHECK; |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
session_status s; |
|
|
|
s.optimistic_unchoke_counter = m_optimistic_unchoke_time_scaler; |
|
s.unchoke_counter = m_unchoke_time_scaler; |
|
|
|
s.num_peers = (int)m_connections.size(); |
|
s.num_unchoked = m_num_unchoked; |
|
s.allowed_upload_slots = m_allowed_upload_slots; |
|
|
|
s.total_redundant_bytes = m_total_redundant_bytes; |
|
s.total_failed_bytes = m_total_failed_bytes; |
|
|
|
s.up_bandwidth_queue = m_upload_rate.queue_size(); |
|
s.down_bandwidth_queue = m_download_rate.queue_size(); |
|
|
|
s.up_bandwidth_bytes_queue = m_upload_rate.queued_bytes(); |
|
s.down_bandwidth_bytes_queue = m_download_rate.queued_bytes(); |
|
|
|
s.disk_write_queue = m_disk_queues[peer_connection::download_channel]; |
|
s.disk_read_queue = m_disk_queues[peer_connection::upload_channel]; |
|
|
|
s.has_incoming_connections = m_incoming_connection; |
|
|
|
// total |
|
s.download_rate = m_stat.download_rate(); |
|
s.total_upload = m_stat.total_upload(); |
|
s.upload_rate = m_stat.upload_rate(); |
|
s.total_download = m_stat.total_download(); |
|
|
|
// payload |
|
s.payload_download_rate = m_stat.transfer_rate(stat::download_payload); |
|
s.total_payload_download = m_stat.total_transfer(stat::download_payload); |
|
s.payload_upload_rate = m_stat.transfer_rate(stat::upload_payload); |
|
s.total_payload_upload = m_stat.total_transfer(stat::upload_payload); |
|
|
|
#ifndef TORRENT_DISABLE_FULL_STATS |
|
// IP-overhead |
|
s.ip_overhead_download_rate = m_stat.transfer_rate(stat::download_ip_protocol); |
|
s.total_ip_overhead_download = m_stat.total_transfer(stat::download_ip_protocol); |
|
s.ip_overhead_upload_rate = m_stat.transfer_rate(stat::upload_ip_protocol); |
|
s.total_ip_overhead_upload = m_stat.total_transfer(stat::upload_ip_protocol); |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
// DHT protocol |
|
s.dht_download_rate = m_stat.transfer_rate(stat::download_dht_protocol); |
|
s.total_dht_download = m_stat.total_transfer(stat::download_dht_protocol); |
|
s.dht_upload_rate = m_stat.transfer_rate(stat::upload_dht_protocol); |
|
s.total_dht_upload = m_stat.total_transfer(stat::upload_dht_protocol); |
|
#else |
|
s.dht_download_rate = 0; |
|
s.total_dht_download = 0; |
|
s.dht_upload_rate = 0; |
|
s.total_dht_upload = 0; |
|
#endif // TORRENT_DISABLE_DHT |
|
|
|
// tracker |
|
s.tracker_download_rate = m_stat.transfer_rate(stat::download_tracker_protocol); |
|
s.total_tracker_download = m_stat.total_transfer(stat::download_tracker_protocol); |
|
s.tracker_upload_rate = m_stat.transfer_rate(stat::upload_tracker_protocol); |
|
s.total_tracker_upload = m_stat.total_transfer(stat::upload_tracker_protocol); |
|
#else |
|
// IP-overhead |
|
s.ip_overhead_download_rate = 0; |
|
s.total_ip_overhead_download = 0; |
|
s.ip_overhead_upload_rate = 0; |
|
s.total_ip_overhead_upload = 0; |
|
|
|
// DHT protocol |
|
s.dht_download_rate = 0; |
|
s.total_dht_download = 0; |
|
s.dht_upload_rate = 0; |
|
s.total_dht_upload = 0; |
|
|
|
// tracker |
|
s.tracker_download_rate = 0; |
|
s.total_tracker_download = 0; |
|
s.tracker_upload_rate = 0; |
|
s.total_tracker_upload = 0; |
|
#endif |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
if (m_dht) |
|
{ |
|
m_dht->dht_status(s); |
|
} |
|
else |
|
#endif |
|
{ |
|
s.dht_nodes = 0; |
|
s.dht_node_cache = 0; |
|
s.dht_torrents = 0; |
|
s.dht_global_nodes = 0; |
|
s.dht_total_allocations = 0; |
|
} |
|
|
|
m_utp_socket_manager.get_status(s.utp_stats); |
|
|
|
int peerlist_size = 0; |
|
for (torrent_map::const_iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
peerlist_size += i->second->get_policy().num_peers(); |
|
} |
|
|
|
s.peerlist_size = peerlist_size; |
|
|
|
boost::system::error_code ec; |
|
s.external_addr_v4 = external_address().external_address(address_v4()).to_string(ec); |
|
|
|
return s; |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_DHT |
|
|
|
void session_impl::start_dht() |
|
{ start_dht(m_dht_state); } |
|
|
|
void session_impl::start_dht(entry const& startup_state) |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
stop_dht(); |
|
m_dht = new dht::dht_tracker(*this, m_udp_socket, m_dht_settings, &startup_state); |
|
|
|
for (std::list<udp::endpoint>::iterator i = m_dht_router_nodes.begin() |
|
, end(m_dht_router_nodes.end()); i != end; ++i) |
|
{ |
|
m_dht->add_router_node(*i); |
|
} |
|
|
|
m_dht->start(startup_state); |
|
|
|
m_udp_socket.subscribe(m_dht.get()); |
|
} |
|
|
|
void session_impl::stop_dht() |
|
{ |
|
if (!m_dht) return; |
|
|
|
/* FIXME: good idea... unfortunately it doesnt work. |
|
if( m_dht->refresh_storage() ) { |
|
// a small chance to refresh to suceed |
|
sleep(5000); |
|
} |
|
*/ |
|
|
|
m_udp_socket.unsubscribe(m_dht.get()); |
|
m_dht->stop(); |
|
m_dht = 0; |
|
} |
|
|
|
void session_impl::set_dht_settings(dht_settings const& settings) |
|
{ |
|
m_dht_settings = settings; |
|
} |
|
|
|
#ifndef TORRENT_NO_DEPRECATE |
|
entry session_impl::dht_state() const |
|
{ |
|
if (!m_dht) return entry(); |
|
return m_dht->state(); |
|
} |
|
#endif |
|
|
|
void session_impl::add_dht_node_name(std::pair<std::string, int> const& node) |
|
{ |
|
if (m_dht) m_dht->add_node(node); |
|
} |
|
|
|
void session_impl::add_dht_router(std::pair<std::string, int> const& node) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
add_outstanding_async("session_impl::on_dht_router_name_lookup"); |
|
#endif |
|
char port[7]; |
|
snprintf(port, sizeof(port), "%d", node.second); |
|
tcp::resolver::query q(node.first, port); |
|
m_host_resolver.async_resolve(q, |
|
boost::bind(&session_impl::on_dht_router_name_lookup, this, _1, _2)); |
|
} |
|
|
|
void session_impl::dht_putDataSigned(std::string const &username, std::string const &resource, bool multi, |
|
entry const &p, std::string const &sig_p, std::string const &sig_user, bool local) |
|
{ |
|
if (m_dht) m_dht->putDataSigned(username, resource, multi, p, sig_p, sig_user, local); |
|
} |
|
|
|
void post_dht_getData(aux::session_impl *si, entry::list_type const&lst) |
|
{ |
|
if( si->m_alerts.should_post<dht_reply_data_alert>() ) { |
|
si->m_alerts.post_alert(dht_reply_data_alert(lst)); |
|
} |
|
} |
|
|
|
void getDataDone_fun(aux::session_impl *si, |
|
std::string const &username, std::string const &resource, bool multi, |
|
bool is_neighbor, bool got_data) |
|
{ |
|
if( si->m_alerts.should_post<dht_reply_data_done_alert>() ) { |
|
si->m_alerts.post_alert( |
|
dht_reply_data_done_alert(username, resource, multi, |
|
is_neighbor, got_data)); |
|
} |
|
} |
|
|
|
void session_impl::dht_getData(std::string const &username, std::string const &resource, bool multi, bool local) |
|
{ |
|
if (m_dht) m_dht->getData(username, resource, multi, |
|
boost::bind( post_dht_getData, this, _1), |
|
boost::bind( getDataDone_fun, this, username, resource, multi, _1, _2), |
|
local); |
|
} |
|
|
|
entry session_impl::dht_getLocalData() const |
|
{ |
|
if( m_dht ) { |
|
entry state = m_dht->state(); |
|
return state["storage_table"]; |
|
} else { |
|
return entry(); |
|
} |
|
} |
|
|
|
void session_impl::on_dht_router_name_lookup(error_code const& e |
|
, tcp::resolver::iterator host) |
|
{ |
|
#if defined TORRENT_ASIO_DEBUGGING |
|
complete_async("session_impl::on_dht_router_name_lookup"); |
|
#endif |
|
// TODO: 1 report errors as alerts |
|
if (e) return; |
|
while (host != tcp::resolver::iterator()) |
|
{ |
|
// router nodes should be added before the DHT is started (and bootstrapped) |
|
udp::endpoint ep(host->endpoint().address(), host->endpoint().port()); |
|
if (m_dht) m_dht->add_router_node(ep); |
|
m_dht_router_nodes.push_back(ep); |
|
++host; |
|
} |
|
} |
|
#endif |
|
|
|
void session_impl::maybe_update_udp_mapping(int nat, int local_port, int external_port) |
|
{ |
|
int local, external, protocol; |
|
if (nat == 0 && m_natpmp.get()) |
|
{ |
|
if (m_udp_mapping[nat] != -1) |
|
{ |
|
if (m_natpmp->get_mapping(m_udp_mapping[nat], local, external, protocol)) |
|
{ |
|
// we already have a mapping. If it's the same, don't do anything |
|
if (local == local_port && external == external_port && protocol == natpmp::udp) |
|
return; |
|
} |
|
m_natpmp->delete_mapping(m_udp_mapping[nat]); |
|
} |
|
m_udp_mapping[nat] = m_natpmp->add_mapping(natpmp::udp |
|
, local_port, external_port); |
|
return; |
|
} |
|
else if (nat == 1 && m_upnp.get()) |
|
{ |
|
if (m_udp_mapping[nat] != -1) |
|
{ |
|
if (m_upnp->get_mapping(m_udp_mapping[nat], local, external, protocol)) |
|
{ |
|
// we already have a mapping. If it's the same, don't do anything |
|
if (local == local_port && external == external_port && protocol == natpmp::udp) |
|
return; |
|
} |
|
m_upnp->delete_mapping(m_udp_mapping[nat]); |
|
} |
|
m_udp_mapping[nat] = m_upnp->add_mapping(upnp::udp |
|
, local_port, external_port); |
|
return; |
|
} |
|
} |
|
|
|
#ifndef TORRENT_DISABLE_ENCRYPTION |
|
void session_impl::set_pe_settings(pe_settings const& settings) |
|
{ |
|
m_pe_settings = settings; |
|
} |
|
#endif |
|
|
|
bool session_impl::is_listening() const |
|
{ |
|
return !m_listen_sockets.empty(); |
|
} |
|
|
|
session_impl::~session_impl() |
|
{ |
|
m_io_service.post(boost::bind(&session_impl::abort, this)); |
|
|
|
// we need to wait for the disk-io thread to |
|
// die first, to make sure it won't post any |
|
// more messages to the io_service containing references |
|
// to disk_io_pool inside the disk_io_thread. Once |
|
// the main thread has handled all the outstanding requests |
|
// we know it's safe to destruct the disk thread. |
|
m_disk_thread.join(); |
|
|
|
#if defined TORRENT_ASIO_DEBUGGING |
|
int counter = 0; |
|
while (log_async()) |
|
{ |
|
sleep(1000); |
|
++counter; |
|
printf("\n==== Waiting to shut down: %d ==== conn-queue: %d connecting: %d timeout (next: %f max: %f)\n\n" |
|
, counter, m_half_open.size(), m_half_open.num_connecting(), m_half_open.next_timeout() |
|
, m_half_open.max_timeout()); |
|
} |
|
async_dec_threads(); |
|
#endif |
|
|
|
if (m_thread) m_thread->join(); |
|
|
|
m_udp_socket.unsubscribe(this); |
|
m_udp_socket.unsubscribe(&m_utp_socket_manager); |
|
m_udp_socket.unsubscribe(&m_tracker_manager); |
|
|
|
TORRENT_ASSERT(m_torrents.empty()); |
|
TORRENT_ASSERT(m_connections.empty()); |
|
TORRENT_ASSERT(m_connections.empty()); |
|
|
|
#ifdef TORRENT_REQUEST_LOGGING |
|
if (m_request_log) fclose(m_request_log); |
|
#endif |
|
|
|
#ifdef TORRENT_STATS |
|
if (m_stats_logger) fclose(m_stats_logger); |
|
#endif |
|
} |
|
|
|
#ifndef TORRENT_NO_DEPRECATE |
|
int session_impl::max_connections() const |
|
{ |
|
return m_settings.connections_limit; |
|
} |
|
|
|
int session_impl::max_uploads() const |
|
{ |
|
return m_settings.unchoke_slots_limit; |
|
} |
|
|
|
int session_impl::max_half_open_connections() const |
|
{ |
|
return m_settings.half_open_limit; |
|
} |
|
|
|
void session_impl::set_local_download_rate_limit(int bytes_per_second) |
|
{ |
|
session_settings s = m_settings; |
|
s.local_download_rate_limit = bytes_per_second; |
|
set_settings(s); |
|
} |
|
|
|
void session_impl::set_local_upload_rate_limit(int bytes_per_second) |
|
{ |
|
session_settings s = m_settings; |
|
s.local_upload_rate_limit = bytes_per_second; |
|
set_settings(s); |
|
} |
|
|
|
void session_impl::set_download_rate_limit(int bytes_per_second) |
|
{ |
|
session_settings s = m_settings; |
|
s.download_rate_limit = bytes_per_second; |
|
set_settings(s); |
|
} |
|
|
|
void session_impl::set_upload_rate_limit(int bytes_per_second) |
|
{ |
|
session_settings s = m_settings; |
|
s.upload_rate_limit = bytes_per_second; |
|
set_settings(s); |
|
} |
|
|
|
void session_impl::set_max_half_open_connections(int limit) |
|
{ |
|
session_settings s = m_settings; |
|
s.half_open_limit = limit; |
|
set_settings(s); |
|
} |
|
|
|
void session_impl::set_max_connections(int limit) |
|
{ |
|
session_settings s = m_settings; |
|
s.connections_limit = limit; |
|
set_settings(s); |
|
} |
|
|
|
void session_impl::set_max_uploads(int limit) |
|
{ |
|
session_settings s = m_settings; |
|
s.unchoke_slots_limit = limit; |
|
set_settings(s); |
|
} |
|
|
|
int session_impl::local_upload_rate_limit() const |
|
{ |
|
return m_local_upload_channel.throttle(); |
|
} |
|
|
|
int session_impl::local_download_rate_limit() const |
|
{ |
|
return m_local_download_channel.throttle(); |
|
} |
|
|
|
int session_impl::upload_rate_limit() const |
|
{ |
|
return m_upload_channel.throttle(); |
|
} |
|
|
|
int session_impl::download_rate_limit() const |
|
{ |
|
return m_download_channel.throttle(); |
|
} |
|
#endif |
|
|
|
void session_impl::update_unchoke_limit() |
|
{ |
|
m_allowed_upload_slots = m_settings.unchoke_slots_limit; |
|
if (m_allowed_upload_slots < 0) |
|
m_allowed_upload_slots = (std::numeric_limits<int>::max)(); |
|
|
|
if (m_settings.num_optimistic_unchoke_slots >= m_allowed_upload_slots / 2) |
|
{ |
|
if (m_alerts.should_post<performance_alert>()) |
|
m_alerts.post_alert(performance_alert(torrent_handle() |
|
, performance_alert::too_many_optimistic_unchoke_slots)); |
|
} |
|
} |
|
|
|
void session_impl::update_rate_settings() |
|
{ |
|
if (m_settings.half_open_limit <= 0) m_settings.half_open_limit |
|
= (std::numeric_limits<int>::max)(); |
|
m_half_open.limit(m_settings.half_open_limit); |
|
|
|
if (m_settings.local_download_rate_limit < 0) |
|
m_settings.local_download_rate_limit = 0; |
|
m_local_download_channel.throttle(m_settings.local_download_rate_limit); |
|
|
|
if (m_settings.local_upload_rate_limit < 0) |
|
m_settings.local_upload_rate_limit = 0; |
|
m_local_upload_channel.throttle(m_settings.local_upload_rate_limit); |
|
|
|
if (m_settings.download_rate_limit < 0) |
|
m_settings.download_rate_limit = 0; |
|
m_download_channel.throttle(m_settings.download_rate_limit); |
|
|
|
if (m_settings.upload_rate_limit < 0) |
|
m_settings.upload_rate_limit = 0; |
|
m_upload_channel.throttle(m_settings.upload_rate_limit); |
|
} |
|
|
|
void session_impl::update_connections_limit() |
|
{ |
|
if (m_settings.connections_limit <= 0) |
|
{ |
|
m_settings.connections_limit = (std::numeric_limits<int>::max)(); |
|
#if TORRENT_USE_RLIMIT |
|
rlimit l; |
|
if (getrlimit(RLIMIT_NOFILE, &l) == 0 |
|
&& l.rlim_cur != RLIM_INFINITY) |
|
{ |
|
m_settings.connections_limit = l.rlim_cur - m_settings.file_pool_size; |
|
if (m_settings.connections_limit < 5) m_settings.connections_limit = 5; |
|
} |
|
#endif |
|
} |
|
|
|
if (num_connections() > m_settings.connections_limit && !m_torrents.empty()) |
|
{ |
|
// if we have more connections that we're allowed, disconnect |
|
// peers from the torrents so that they are all as even as possible |
|
|
|
int to_disconnect = num_connections() - m_settings.connections_limit; |
|
|
|
int last_average = 0; |
|
int average = m_settings.connections_limit / m_torrents.size(); |
|
|
|
// the number of slots that are unused by torrents |
|
int extra = m_settings.connections_limit % m_torrents.size(); |
|
|
|
// run 3 iterations of this, then we're probably close enough |
|
for (int iter = 0; iter < 4; ++iter) |
|
{ |
|
// the number of torrents that are above average |
|
int num_above = 0; |
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
int num = i->second->num_peers(); |
|
if (num <= last_average) continue; |
|
if (num > average) ++num_above; |
|
if (num < average) extra += average - num; |
|
} |
|
|
|
// distribute extra among the torrents that are above average |
|
if (num_above == 0) num_above = 1; |
|
last_average = average; |
|
average += extra / num_above; |
|
if (extra == 0) break; |
|
// save the remainder for the next iteration |
|
extra = extra % num_above; |
|
} |
|
|
|
for (torrent_map::iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
int num = i->second->num_peers(); |
|
if (num <= average) continue; |
|
|
|
// distribute the remainder |
|
int my_average = average; |
|
if (extra > 0) |
|
{ |
|
++my_average; |
|
--extra; |
|
} |
|
|
|
int disconnect = (std::min)(to_disconnect, num - my_average); |
|
to_disconnect -= disconnect; |
|
i->second->disconnect_peers(disconnect |
|
, error_code(errors::too_many_connections, get_libtorrent_category())); |
|
} |
|
} |
|
} |
|
|
|
void session_impl::set_alert_dispatch(boost::function<void(std::auto_ptr<alert>)> const& fun) |
|
{ |
|
m_alerts.set_dispatch_function(fun); |
|
} |
|
|
|
std::auto_ptr<alert> session_impl::pop_alert() |
|
{ |
|
return m_alerts.get(); |
|
} |
|
|
|
void session_impl::pop_alerts(std::deque<alert*>* alerts) |
|
{ |
|
m_alerts.get_all(alerts); |
|
} |
|
|
|
alert const* session_impl::wait_for_alert(time_duration max_wait) |
|
{ |
|
return m_alerts.wait_for_alert(max_wait); |
|
} |
|
|
|
void session_impl::set_alert_mask(boost::uint32_t m) |
|
{ |
|
m_alerts.set_alert_mask(m); |
|
} |
|
|
|
#ifndef TORRENT_NO_DEPRECATE |
|
size_t session_impl::set_alert_queue_size_limit(size_t queue_size_limit_) |
|
{ |
|
m_settings.alert_queue_size = queue_size_limit_; |
|
return m_alerts.set_alert_queue_size_limit(queue_size_limit_); |
|
} |
|
#endif |
|
|
|
void session_impl::start_lsd() |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
if (m_lsd) return; |
|
|
|
m_lsd = new lsd(m_io_service |
|
, m_listen_interface.address() |
|
, boost::bind(&session_impl::on_lsd_peer, this, _1, _2)); |
|
} |
|
|
|
natpmp* session_impl::start_natpmp() |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
if (m_natpmp) return m_natpmp.get(); |
|
|
|
// the natpmp constructor may fail and call the callbacks |
|
// into the session_impl. |
|
natpmp* n = new (std::nothrow) natpmp(m_io_service |
|
, m_listen_interface.address() |
|
, boost::bind(&session_impl::on_port_mapping |
|
, this, _1, _2, _3, _4, 0) |
|
, boost::bind(&session_impl::on_port_map_log |
|
, this, _1, 0)); |
|
if (n == 0) return 0; |
|
|
|
m_natpmp = n; |
|
|
|
if (m_listen_interface.port() > 0) |
|
{ |
|
remap_tcp_ports(1, m_listen_interface.port(), ssl_listen_port()); |
|
} |
|
if (m_udp_socket.is_open()) |
|
{ |
|
m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp |
|
, m_listen_interface.port(), m_listen_interface.port()); |
|
} |
|
return n; |
|
} |
|
|
|
upnp* session_impl::start_upnp() |
|
{ |
|
INVARIANT_CHECK; |
|
|
|
if (m_upnp) return m_upnp.get(); |
|
|
|
// the upnp constructor may fail and call the callbacks |
|
upnp* u = new (std::nothrow) upnp(m_io_service |
|
, m_half_open |
|
, m_listen_interface.address() |
|
, m_settings.user_agent |
|
, boost::bind(&session_impl::on_port_mapping |
|
, this, _1, _2, _3, _4, 1) |
|
, boost::bind(&session_impl::on_port_map_log |
|
, this, _1, 1) |
|
, m_settings.upnp_ignore_nonrouters); |
|
|
|
if (u == 0) return 0; |
|
|
|
m_upnp = u; |
|
|
|
m_upnp->discover_device(); |
|
if (m_listen_interface.port() > 0 || ssl_listen_port() > 0) |
|
{ |
|
remap_tcp_ports(2, m_listen_interface.port(), ssl_listen_port()); |
|
} |
|
if (m_udp_socket.is_open()) |
|
{ |
|
m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp |
|
, m_listen_interface.port(), m_listen_interface.port()); |
|
} |
|
return u; |
|
} |
|
|
|
void session_impl::stop_lsd() |
|
{ |
|
if (m_lsd.get()) |
|
m_lsd->close(); |
|
m_lsd = 0; |
|
} |
|
|
|
void session_impl::stop_natpmp() |
|
{ |
|
if (m_natpmp.get()) |
|
m_natpmp->close(); |
|
m_natpmp = 0; |
|
} |
|
|
|
void session_impl::stop_upnp() |
|
{ |
|
if (m_upnp.get()) |
|
{ |
|
m_upnp->close(); |
|
m_udp_mapping[1] = -1; |
|
m_tcp_mapping[1] = -1; |
|
m_twister_tcp_mapping[1] = -1; |
|
#ifdef TORRENT_USE_OPENSSL |
|
m_ssl_mapping[1] = -1; |
|
#endif |
|
} |
|
m_upnp = 0; |
|
} |
|
|
|
external_ip const& session_impl::external_address() const |
|
{ return m_external_ip; } |
|
|
|
// this is the DHT observer version. DHT is the implied source |
|
void session_impl::set_external_address(address const& ip |
|
, address const& source) |
|
{ |
|
set_external_address(ip, source_dht, source); |
|
} |
|
|
|
void session_impl::set_external_address(address const& ip |
|
, int source_type, address const& source) |
|
{ |
|
#if defined TORRENT_VERBOSE_LOGGING |
|
session_log(": set_external_address(%s, %d, %s)", print_address(ip).c_str() |
|
, source_type, print_address(source).c_str()); |
|
#endif |
|
|
|
if (!m_external_ip.cast_vote(ip, source_type, source)) return; |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING |
|
session_log(" external IP updated"); |
|
#endif |
|
|
|
if (m_alerts.should_post<external_ip_alert>()) |
|
m_alerts.post_alert(external_ip_alert(ip)); |
|
|
|
// since we have a new external IP now, we need to |
|
// restart the DHT with a new node ID |
|
#ifndef TORRENT_DISABLE_DHT |
|
// TODO: 1 we only need to do this if our global IPv4 address has changed |
|
// since the DHT (currently) only supports IPv4. Since restarting the DHT |
|
// is kind of expensive, it would be nice to not do it unnecessarily |
|
if (m_dht) |
|
{ |
|
entry s = m_dht->state(); |
|
int cur_state = 0; |
|
int prev_state = 0; |
|
entry* nodes1 = s.find_key("nodes"); |
|
if (nodes1 && nodes1->type() == entry::list_t) cur_state = nodes1->list().size(); |
|
entry* nodes2 = m_dht_state.find_key("nodes"); |
|
if (nodes2 && nodes2->type() == entry::list_t) prev_state = nodes2->list().size(); |
|
if (cur_state > prev_state) m_dht_state = s; |
|
start_dht(m_dht_state); |
|
} |
|
#endif |
|
} |
|
|
|
void session_impl::free_disk_buffer(char* buf) |
|
{ |
|
m_disk_thread.free_buffer(buf); |
|
} |
|
|
|
char* session_impl::allocate_disk_buffer(char const* category) |
|
{ |
|
return m_disk_thread.allocate_buffer(category); |
|
} |
|
|
|
char* session_impl::allocate_buffer() |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
#ifdef TORRENT_DISK_STATS |
|
TORRENT_ASSERT(m_buffer_allocations >= 0); |
|
m_buffer_allocations++; |
|
m_buffer_usage_logger << log_time() << " protocol_buffer: " |
|
<< (m_buffer_allocations * send_buffer_size) << std::endl; |
|
#endif |
|
#ifdef TORRENT_DISABLE_POOL_ALLOCATOR |
|
int num_bytes = send_buffer_size; |
|
return (char*)malloc(num_bytes); |
|
#else |
|
return (char*)m_send_buffers.malloc(); |
|
#endif |
|
} |
|
|
|
#ifdef TORRENT_DISK_STATS |
|
void session_impl::log_buffer_usage() |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
int send_buffer_capacity = 0; |
|
int used_send_buffer = 0; |
|
for (connection_map::const_iterator i = m_connections.begin() |
|
, end(m_connections.end()); i != end; ++i) |
|
{ |
|
send_buffer_capacity += (*i)->send_buffer_capacity(); |
|
used_send_buffer += (*i)->send_buffer_size(); |
|
} |
|
TORRENT_ASSERT(send_buffer_capacity >= used_send_buffer); |
|
m_buffer_usage_logger << log_time() << " send_buffer_size: " << send_buffer_capacity << std::endl; |
|
m_buffer_usage_logger << log_time() << " used_send_buffer: " << used_send_buffer << std::endl; |
|
m_buffer_usage_logger << log_time() << " send_buffer_utilization: " |
|
<< (used_send_buffer * 100.f / send_buffer_capacity) << std::endl; |
|
} |
|
#endif |
|
|
|
void session_impl::free_buffer(char* buf) |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
#ifdef TORRENT_DISK_STATS |
|
m_buffer_allocations--; |
|
TORRENT_ASSERT(m_buffer_allocations >= 0); |
|
m_buffer_usage_logger << log_time() << " protocol_buffer: " |
|
<< (m_buffer_allocations * send_buffer_size) << std::endl; |
|
#endif |
|
#ifdef TORRENT_DISABLE_POOL_ALLOCATOR |
|
free(buf); |
|
#else |
|
m_send_buffers.free(buf); |
|
#endif |
|
} |
|
|
|
#if defined TORRENT_DEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS |
|
void session_impl::check_invariant() const |
|
{ |
|
TORRENT_ASSERT(is_network_thread()); |
|
|
|
if (m_settings.unchoke_slots_limit < 0 |
|
&& m_settings.choking_algorithm == session_settings::fixed_slots_choker) |
|
TORRENT_ASSERT(m_allowed_upload_slots == (std::numeric_limits<int>::max)()); |
|
|
|
int num_checking = 0; |
|
int num_queued_for_checking = 0; |
|
for (check_queue_t::const_iterator i = m_queued_for_checking.begin() |
|
, end(m_queued_for_checking.end()); i != end; ++i) |
|
{ |
|
if ((*i)->state() == torrent_status::checking_files) ++num_checking; |
|
else if ((*i)->state() == torrent_status::queued_for_checking) |
|
{ |
|
++num_queued_for_checking; |
|
} |
|
} |
|
|
|
// the queue is either empty, or it has exactly one checking torrent in it |
|
TORRENT_ASSERT(m_queued_for_checking.empty() || num_checking == 1 || (m_paused && num_checking == 0)); |
|
// TORRENT_ASSERT(m_queued_for_checking.size() == num_queued_for_checking); |
|
|
|
std::set<int> unique; |
|
int num_active_downloading = 0; |
|
int num_active_finished = 0; |
|
int total_downloaders = 0; |
|
for (torrent_map::const_iterator i = m_torrents.begin() |
|
, end(m_torrents.end()); i != end; ++i) |
|
{ |
|
boost::shared_ptr<torrent> t = i->second; |
|
if (t->is_active_download()) ++num_active_downloading; |
|
else if (t->is_active_finished()) ++num_active_finished; |
|
|
|
int pos = t->queue_position(); |
|
if (pos < 0) |
|
{ |
|
TORRENT_ASSERT(pos == -1); |
|
continue; |
|
} |
|
++total_downloaders; |
|
|
|
unique.insert(t->queue_position()); |
|
} |
|
TORRENT_ASSERT(int(unique.size()) == total_downloaders); |
|
TORRENT_ASSERT(num_active_downloading == m_num_active_downloading); |
|
TORRENT_ASSERT(num_active_finished == m_num_active_finished); |
|
|
|
std::set<peer_connection*> unique_peers; |
|
TORRENT_ASSERT(m_settings.connections_limit > 0); |
|
if (m_settings.choking_algorithm == session_settings::auto_expand_choker) |
|
TORRENT_ASSERT(m_allowed_upload_slots >= m_settings.unchoke_slots_limit); |
|
int unchokes = 0; |
|
int num_optimistic = 0; |
|
int disk_queue[2] = {0, 0}; |
|
for (connection_map::const_iterator i = m_connections.begin(); |
|
i != m_connections.end(); ++i) |
|
{ |
|
TORRENT_ASSERT(*i); |
|
boost::shared_ptr<torrent> t = (*i)->associated_torrent().lock(); |
|
TORRENT_ASSERT(unique_peers.find(i->get()) == unique_peers.end()); |
|
unique_peers.insert(i->get()); |
|
|
|
if ((*i)->m_channel_state[0] & peer_info::bw_disk) ++disk_queue[0]; |
|
if ((*i)->m_channel_state[1] & peer_info::bw_disk) ++disk_queue[1]; |
|
|
|
peer_connection* p = i->get(); |
|
TORRENT_ASSERT(!p->is_disconnecting()); |
|
if (p->ignore_unchoke_slots()) continue; |
|
if (!p->is_choked()) ++unchokes; |
|
if (p->peer_info_struct() |
|
&& p->peer_info_struct()->optimistically_unchoked) |
|
{ |
|
++num_optimistic; |
|
TORRENT_ASSERT(!p->is_choked()); |
|
} |
|
if (t && p->peer_info_struct() && !p->peer_info_struct()->web_seed) |
|
{ |
|
TORRENT_ASSERT(t->get_policy().has_connection(p)); |
|
} |
|
} |
|
|
|
TORRENT_ASSERT(disk_queue[0] == m_disk_queues[0]); |
|
TORRENT_ASSERT(disk_queue[1] == m_disk_queues[1]); |
|
|
|
if (m_settings.num_optimistic_unchoke_slots) |
|
{ |
|
TORRENT_ASSERT(num_optimistic <= m_settings.num_optimistic_unchoke_slots); |
|
} |
|
|
|
if (m_num_unchoked != unchokes) |
|
{ |
|
TORRENT_ASSERT(false); |
|
} |
|
for (torrent_map::const_iterator j |
|
= m_torrents.begin(); j != m_torrents.end(); ++j) |
|
{ |
|
TORRENT_ASSERT(boost::get_pointer(j->second)); |
|
} |
|
} |
|
#endif |
|
|
|
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING |
|
tracker_logger::tracker_logger(session_impl& ses): m_ses(ses) {} |
|
void tracker_logger::tracker_warning(tracker_request const& req |
|
, std::string const& str) |
|
{ |
|
debug_log("*** tracker warning: %s", str.c_str()); |
|
} |
|
|
|
void tracker_logger::tracker_response(tracker_request const& |
|
, libtorrent::address const& tracker_ip |
|
, std::list<address> const& ip_list |
|
, std::vector<peer_entry>& peers |
|
, int interval |
|
, int min_interval |
|
, int complete |
|
, int incomplete |
|
, int downloaded |
|
, address const& external_ip |
|
, std::string const& tracker_id) |
|
{ |
|
std::string s; |
|
s = "TRACKER RESPONSE:\n"; |
|
char tmp[200]; |
|
snprintf(tmp, 200, "interval: %d\nmin_interval: %d\npeers:\n", interval, min_interval); |
|
s += tmp; |
|
for (std::vector<peer_entry>::const_iterator i = peers.begin(); |
|
i != peers.end(); ++i) |
|
{ |
|
char pid[41]; |
|
to_hex((const char*)&i->pid[0], 20, pid); |
|
if (i->pid.is_all_zeros()) pid[0] = 0; |
|
|
|
snprintf(tmp, 200, " %-16s %-5d %s\n", i->ip.c_str(), i->port, pid); |
|
s += tmp; |
|
} |
|
snprintf(tmp, 200, "external ip: %s\n", print_address(external_ip).c_str()); |
|
s += tmp; |
|
debug_log("%s", s.c_str()); |
|
} |
|
|
|
void tracker_logger::tracker_request_timed_out( |
|
tracker_request const&) |
|
{ |
|
debug_log("*** tracker timed out"); |
|
} |
|
|
|
void tracker_logger::tracker_request_error(tracker_request const& r |
|
, int response_code, error_code const& ec, const std::string& str |
|
, int retry_interval) |
|
{ |
|
debug_log("*** tracker error: %d: %s %s" |
|
, response_code, ec.message().c_str(), str.c_str()); |
|
} |
|
|
|
void tracker_logger::debug_log(const char* fmt, ...) const |
|
{ |
|
if (!m_ses.m_logger) return; |
|
|
|
va_list v; |
|
va_start(v, fmt); |
|
|
|
char usr[1024]; |
|
vsnprintf(usr, sizeof(usr), fmt, v); |
|
va_end(v); |
|
char buf[1280]; |
|
snprintf(buf, sizeof(buf), "%s: %s\n", time_now_string(), usr); |
|
// printf is the bitcoin/util.h logger |
|
printf("%s", buf); |
|
//(*m_ses.m_logger) << buf; |
|
} |
|
#endif |
|
}} |
|
|
|
|