From 0c8aee014c8077bc86db113172bd6cc72049d799 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sat, 28 Feb 2015 19:51:15 +0000 Subject: [PATCH] implemented utp extension header to indicate the reason to close the connection --- CMakeLists.txt | 1 + Jamfile | 1 + include/libtorrent/Makefile.am | 1 + include/libtorrent/alert_types.hpp | 8 +- include/libtorrent/close_reason.hpp | 158 ++++++++++++++++++++++++ include/libtorrent/socket_type.hpp | 5 + include/libtorrent/utp_stream.hpp | 29 ++++- src/Makefile.am | 1 + src/alert.cpp | 11 +- src/bt_peer_connection.cpp | 5 +- src/close_reason.cpp | 182 ++++++++++++++++++++++++++++ src/peer_connection.cpp | 36 ++++-- src/session_impl.cpp | 3 +- src/socket_type.cpp | 30 +++++ src/utp_stream.cpp | 103 +++++++++++++--- 15 files changed, 534 insertions(+), 40 deletions(-) create mode 100644 include/libtorrent/close_reason.hpp create mode 100644 src/close_reason.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fd7f10e7..c993d8221 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ set(sources bloom_filter chained_buffer choker + close_reason crc32c create_torrent disk_buffer_holder diff --git a/Jamfile b/Jamfile index 09fc26e27..fa1cf8e6a 100755 --- a/Jamfile +++ b/Jamfile @@ -533,6 +533,7 @@ SOURCES = bloom_filter chained_buffer choker + close_reason crc32c create_torrent disk_buffer_holder diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am index cf1212055..8fc551cae 100644 --- a/include/libtorrent/Makefile.am +++ b/include/libtorrent/Makefile.am @@ -26,6 +26,7 @@ nobase_include_HEADERS = \ byteswap.hpp \ chained_buffer.hpp \ choker.hpp \ + close_reason.hpp \ config.hpp \ ConvertUTF.h \ copy_ptr.hpp \ diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 766c34a1c..58c2644ef 100644 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/stat.hpp" #include "libtorrent/rss.hpp" // for feed_handle #include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/close_reason.hpp" namespace libtorrent { @@ -703,11 +704,13 @@ namespace libtorrent { // internal peer_disconnected_alert(torrent_handle const& h, tcp::endpoint const& ep - , peer_id const& peer_id, operation_t op, int type, error_code const& e) + , peer_id const& peer_id, operation_t op, int type, error_code const& e + , close_reason_t r) : peer_alert(h, ep, peer_id) , socket_type(type) , operation(op) , error(e) + , reason(r) { #ifndef TORRENT_NO_DEPRECATE msg = convert_from_native(error.message()); @@ -729,6 +732,9 @@ namespace libtorrent // tells you what error caused peer to disconnect. error_code error; + // the reason the peer disconnected (if specified) + close_reason_t reason; + #ifndef TORRENT_NO_DEPRECATE std::string msg; #endif diff --git a/include/libtorrent/close_reason.hpp b/include/libtorrent/close_reason.hpp new file mode 100644 index 000000000..b88cdc976 --- /dev/null +++ b/include/libtorrent/close_reason.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CLOSE_REASON_HPP +#define TORRENT_CLOSE_REASON_HPP + +#include "libtorrent/error_code.hpp" + +namespace libtorrent +{ + // these are all the reasons to disconnect a peer + // all reasons caused by the peer sending unexpected data + // are 256 and up. + enum close_reason_t + { + // no reason specified. Generic close. + close_no_reason = 0, + + // we're already connected to + close_duplicate_peer_id, + + // this torrent has been removed, paused or stopped from this client. + close_torrent_removed, + + // client failed to allocate necessary memory for this peer connection + close_no_memory, + + // the source port of this peer is blocked + close_port_blocked, + + // the source IP has been blocked + close_blocked, + + // both ends of the connection are upload-only. staying connected would + // be redundant + close_upload_to_upload, + + // connection was closed because the other end is upload only and does + // not have any pieces we're interested in + close_not_interested_upload_only, + + // peer connection timed out (generic timeout) + close_timeout, + + // the peers have not been interested in each other for a very long time. + // disconnect + close_timed_out_interest, + + // the peer has not sent any message in a long time. + close_timed_out_activity, + + // the peer did not complete the handshake in too long + close_timed_out_handshake, + + // the peer sent an interested message, but did not send a request + // after a very long time after being unchoked. + close_timed_out_request, + + // the encryption mode is blocked + close_protocol_blocked, + + // the peer was disconnected in the hopes of finding a better peer + // in the swarm + close_peer_churn, + + // we have too many peers connected + close_too_many_connections, + + // we have too many file-descriptors open + close_too_many_files, + + // the encryption handshake failed + close_encryption_error = 256, + + // the info hash sent as part of the handshake was not what we expected + close_invalid_info_hash, + + close_self_connection, + + // the metadata received matched the info-hash, but failed to parse. + // this is either someone finding a SHA1 collision, or the author of + // the magnet link creating it from an invalid torrent + close_invalid_metadata, + + // the advertised metadata size + close_metadata_too_big, + + // invalid bittorrent messages + close_message_too_big, + close_invalid_message_id, + close_invalid_message, + close_invalid_piece_message, + close_invalid_have_message, + close_invalid_bitfield_message, + close_invalid_choke_message, + close_invalid_unchoke_message, + close_invalid_interested_message, + close_invalid_not_interested_message, + close_invalid_request_message, + close_invalid_reject_message, + close_invalid_allow_fast_message, + close_invalid_extended_message, + close_invalid_cancel_message, + close_invalid_dht_port_message, + close_invalid_suggest_message, + close_invalid_have_all_message, + close_invalid_dont_have_message, + close_invalid_have_none_message, + close_invalid_pex_message, + close_invalid_metadata_request_message, + close_invalid_metadata_message, + close_invalid_metadata_offset, + + // the peer sent a request while being choked + close_request_when_choked, + + // the peer sent corrupt data + close_corrupt_pieces, + + close_pex_message_too_big, + close_pex_too_frequent, + + }; + + close_reason_t error_to_close_reason(error_code const& ec); +} + +#endif + diff --git a/include/libtorrent/socket_type.hpp b/include/libtorrent/socket_type.hpp index 96dbf5946..3c30995be 100644 --- a/include/libtorrent/socket_type.hpp +++ b/include/libtorrent/socket_type.hpp @@ -202,6 +202,11 @@ namespace libtorrent void open(protocol_type const& p, error_code& ec); void close(error_code& ec); + + // this is only relevant for uTP connections + void set_close_reason(boost::uint16_t code); + boost::uint16_t get_close_reason(); + endpoint_type local_endpoint(error_code& ec) const; endpoint_type remote_endpoint(error_code& ec) const; void bind(endpoint_type const& endpoint, error_code& ec); diff --git a/include/libtorrent/utp_stream.hpp b/include/libtorrent/utp_stream.hpp index 4e32d5e3f..ca19f8625 100644 --- a/include/libtorrent/utp_stream.hpp +++ b/include/libtorrent/utp_stream.hpp @@ -121,6 +121,11 @@ namespace libtorrent enum utp_socket_state_t { ST_DATA, ST_FIN, ST_STATE, ST_RESET, ST_SYN, NUM_TYPES }; +// extension headers. 2 is skipped because there is a deprecated +// extension with that number in the wild +enum utp_extensions_t +{ utp_no_extension = 0, utp_sack = 1, utp_close_reason = 3 }; + struct utp_header { unsigned char type_ver; @@ -215,7 +220,8 @@ public: #endif template - error_code get_option(GettableSocketOption&, error_code& ec) { return ec; } + error_code get_option(GettableSocketOption&, error_code& ec) + { return ec; } error_code cancel(error_code& ec) { @@ -225,12 +231,19 @@ public: void close(); void close(error_code const& /*ec*/) { close(); } + + void set_close_reason(boost::uint16_t code); + boost::uint16_t get_close_reason(); + bool is_open() const { return m_open; } int read_buffer_size() const; - static void on_read(void* self, size_t bytes_transferred, error_code const& ec, bool kill); - static void on_write(void* self, size_t bytes_transferred, error_code const& ec, bool kill); + static void on_read(void* self, size_t bytes_transferred + , error_code const& ec, bool kill); + static void on_write(void* self, size_t bytes_transferred + , error_code const& ec, bool kill); static void on_connect(void* self, error_code const& ec, bool kill); + static void on_close_reason(void* self, boost::uint16_t reason); void add_read_buffer(void* buf, size_t len); void issue_read(); @@ -421,14 +434,16 @@ public: { if (m_impl == 0) { - m_io_service.post(boost::bind(handler, asio::error::not_connected, 0)); + m_io_service.post(boost::bind(handler + , asio::error::not_connected, 0)); return; } TORRENT_ASSERT(!m_write_handler); if (m_write_handler) { - m_io_service.post(boost::bind(handler, asio::error::operation_not_supported, 0)); + m_io_service.post(boost::bind(handler + , asio::error::operation_not_supported, 0)); return; } @@ -453,7 +468,7 @@ public: issue_write(); } -//private: +private: void cancel_handlers(error_code const&); @@ -464,6 +479,8 @@ public: asio::io_service& m_io_service; utp_socket_impl* m_impl; + boost::uint16_t m_incoming_close_reason; + // this field requires another 8 bytes (including padding) bool m_open; }; diff --git a/src/Makefile.am b/src/Makefile.am index 618644c9f..b9184123c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,6 +49,7 @@ libtorrent_rasterbar_la_SOURCES = \ bt_peer_connection.cpp \ chained_buffer.cpp \ choker.cpp \ + close_reason.cpp \ ConvertUTF.cpp \ crc32c.cpp \ create_torrent.cpp \ diff --git a/src/alert.cpp b/src/alert.cpp index b918f5865..7f090a400 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -575,8 +575,10 @@ namespace libtorrent { std::string peer_error_alert::message() const { char msg[200]; - snprintf(msg, sizeof(msg), "%s peer error [%s] [%s]: %s", peer_alert::message().c_str() - , operation_name(operation), error.category().name(), convert_from_native(error.message()).c_str()); + snprintf(msg, sizeof(msg), "%s peer error [%s] [%s]: %s" + , peer_alert::message().c_str() + , operation_name(operation), error.category().name() + , convert_from_native(error.message()).c_str()); return msg; } @@ -630,11 +632,12 @@ namespace libtorrent { std::string peer_disconnected_alert::message() const { char msg[600]; - snprintf(msg, sizeof(msg), "%s disconnecting (%s) [%s] [%s]: %s" + snprintf(msg, sizeof(msg), "%s disconnecting (%s) [%s] [%s]: %s (reason: %d)" , peer_alert::message().c_str() , socket_type_str[socket_type] , operation_name(operation), error.category().name() - , convert_from_native(error.message()).c_str()); + , convert_from_native(error.message()).c_str() + , int(reason)); return msg; } diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 2f187091f..dba479a5b 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -644,8 +644,6 @@ namespace libtorrent TORRENT_ASSERT(!m_rc4.get()); m_rc4 = boost::make_shared(); - m_rc4->set_incoming_key(&remote_key[0], 20); - m_rc4->set_outgoing_key(&local_key[0], 20); if (!m_rc4) { @@ -653,6 +651,9 @@ namespace libtorrent return; } + m_rc4->set_incoming_key(&remote_key[0], 20); + m_rc4->set_outgoing_key(&local_key[0], 20); + #ifdef TORRENT_LOGGING peer_log(" computed RC4 keys"); #endif diff --git a/src/close_reason.cpp b/src/close_reason.cpp new file mode 100644 index 000000000..c86bb1b24 --- /dev/null +++ b/src/close_reason.cpp @@ -0,0 +1,182 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/close_reason.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" + +#include + +namespace libtorrent +{ + + close_reason_t error_to_close_reason(error_code const& ec) + { + if (ec.category() == get_libtorrent_category()) + { +#define TORRENT_MAP(error, close_reason) \ + case errors:: error : \ + return close_reason; + + switch (ec.value()) + { + TORRENT_MAP(invalid_swarm_metadata, close_invalid_metadata) + TORRENT_MAP(session_is_closing, close_torrent_removed) + TORRENT_MAP(peer_sent_empty_piece, close_invalid_piece_message) + TORRENT_MAP(mismatching_info_hash, close_invalid_info_hash) + TORRENT_MAP(port_blocked, close_port_blocked) + TORRENT_MAP(destructing_torrent, close_torrent_removed) + TORRENT_MAP(timed_out, close_timeout) + TORRENT_MAP(upload_upload_connection, close_upload_to_upload) + TORRENT_MAP(uninteresting_upload_peer, close_not_interested_upload_only) + TORRENT_MAP(invalid_info_hash, close_invalid_info_hash) + TORRENT_MAP(torrent_paused, close_torrent_removed) + TORRENT_MAP(invalid_have, close_invalid_have_message) + TORRENT_MAP(invalid_bitfield_size, close_invalid_bitfield_message) + TORRENT_MAP(too_many_requests_when_choked, close_request_when_choked) + TORRENT_MAP(invalid_piece, close_invalid_piece_message) + TORRENT_MAP(invalid_piece_size, close_invalid_piece_message) + TORRENT_MAP(no_memory, close_no_memory) + TORRENT_MAP(torrent_aborted, close_torrent_removed) + TORRENT_MAP(self_connection, close_self_connection) + TORRENT_MAP(timed_out_no_interest, close_timed_out_interest) + TORRENT_MAP(timed_out_inactivity, close_timed_out_activity) + TORRENT_MAP(timed_out_no_handshake, close_timed_out_handshake) + TORRENT_MAP(timed_out_no_request, close_timed_out_request) + TORRENT_MAP(invalid_choke, close_invalid_choke_message) + TORRENT_MAP(invalid_unchoke, close_invalid_unchoke_message) + TORRENT_MAP(invalid_interested, close_invalid_interested_message) + TORRENT_MAP(invalid_not_interested, close_invalid_not_interested_message) + TORRENT_MAP(invalid_request, close_invalid_request_message) + TORRENT_MAP(invalid_hash_list, close_invalid_message) + TORRENT_MAP(invalid_hash_piece, close_invalid_message) + TORRENT_MAP(invalid_cancel, close_invalid_cancel_message) + TORRENT_MAP(invalid_dht_port, close_invalid_dht_port_message) + TORRENT_MAP(invalid_suggest, close_invalid_suggest_message) + TORRENT_MAP(invalid_have_all, close_invalid_have_all_message) + TORRENT_MAP(invalid_have_none, close_invalid_have_none_message) + TORRENT_MAP(invalid_reject, close_invalid_reject_message) + TORRENT_MAP(invalid_allow_fast, close_invalid_allow_fast_message) + TORRENT_MAP(invalid_extended, close_invalid_extended_message) + TORRENT_MAP(invalid_message, close_invalid_message_id) + TORRENT_MAP(sync_hash_not_found, close_encryption_error) + TORRENT_MAP(invalid_encryption_constant, close_encryption_error) + TORRENT_MAP(no_plaintext_mode, close_protocol_blocked) + TORRENT_MAP(no_rc4_mode, close_protocol_blocked) + TORRENT_MAP(unsupported_encryption_mode_selected, close_protocol_blocked) + TORRENT_MAP(invalid_pad_size, close_encryption_error) + TORRENT_MAP(invalid_encrypt_handshake, close_encryption_error) + TORRENT_MAP(no_incoming_encrypted, close_protocol_blocked) + TORRENT_MAP(no_incoming_regular, close_protocol_blocked) + TORRENT_MAP(duplicate_peer_id, close_duplicate_peer_id) + TORRENT_MAP(torrent_removed, close_torrent_removed) + TORRENT_MAP(packet_too_large, close_message_too_big) + TORRENT_MAP(torrent_not_ready, close_torrent_removed) + TORRENT_MAP(session_closing, close_torrent_removed) + TORRENT_MAP(optimistic_disconnect, close_peer_churn) + TORRENT_MAP(torrent_finished, close_upload_to_upload) + TORRENT_MAP(too_many_corrupt_pieces, close_corrupt_pieces) + TORRENT_MAP(too_many_connections, close_too_many_connections) + TORRENT_MAP(peer_banned, close_blocked) + TORRENT_MAP(stopping_torrent, close_torrent_removed) + TORRENT_MAP(metadata_too_large, close_metadata_too_big) + TORRENT_MAP(invalid_metadata_size, close_metadata_too_big) + TORRENT_MAP(invalid_metadata_request, close_invalid_metadata_request_message) + TORRENT_MAP(invalid_metadata_offset, close_invalid_metadata_offset) + TORRENT_MAP(invalid_metadata_message, close_invalid_metadata_message) + TORRENT_MAP(pex_message_too_large, close_pex_message_too_big) + TORRENT_MAP(invalid_pex_message, close_invalid_pex_message) + TORRENT_MAP(invalid_lt_tracker_message, close_invalid_message) + TORRENT_MAP(too_frequent_pex, close_pex_too_frequent) + TORRENT_MAP(invalid_dont_have, close_invalid_dont_have_message) + TORRENT_MAP(requires_ssl_connection, close_protocol_blocked) + TORRENT_MAP(invalid_ssl_cert, close_blocked) + TORRENT_MAP(not_an_ssl_torrent, close_blocked) + TORRENT_MAP(banned_by_port_filter, close_port_blocked) + +#ifdef TORRENT_USE_ASSERTS + case errors::redirecting: + return close_no_reason; +#endif + + default: + // we should include this error code in the map + fprintf(stderr, "(%s : %d) %s\n", ec.category().name(), ec.value() + , ec.message().c_str()); + TORRENT_ASSERT(false); + return close_no_reason; + } + } + else if (ec.category() == boost::asio::error::get_misc_category()) + { + switch (ec.value()) + { + case boost::asio::error::eof: + return close_no_reason; + } + } + else if (ec.category() == boost::system::system_category()) + { + switch (ec.value()) + { +#ifdef TORRENT_USE_ASSERTS + case boost::system::errc::connection_reset: + case boost::system::errc::broken_pipe: + return close_no_reason; +#endif + case boost::system::errc::timed_out: + return close_timeout; + case boost::system::errc::too_many_files_open: + case boost::system::errc::too_many_files_open_in_system: + return close_too_many_files; + case boost::system::errc::not_enough_memory: + case boost::system::errc::no_buffer_space: + return close_no_memory; + } + } + else if (ec.category() == get_http_category()) + { + return close_no_memory; + } + + fprintf(stderr, "(%s : %d) %s\n", ec.category().name(), ec.value() + , ec.message().c_str()); + + // we should proboably include this in the map + TORRENT_ASSERT(false); + + // TODO: map some system errors too? + + return close_no_reason; + } +} + diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index bbbf40a62..fbaef3223 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -70,6 +70,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager #include "libtorrent/ip_filter.hpp" #include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/close_reason.hpp" #ifdef TORRENT_DEBUG #include @@ -2290,8 +2291,13 @@ namespace libtorrent // every ten invalid request, remind the peer that it's choked if (!m_peer_interested && m_num_invalid_requests % 10 == 0 && m_choked) { + // TODO: 2 this should probably be based on time instead of number + // of request messages. For a very high throughput connection, 300 + // may be a legitimate number of requests to have in flight when + // getting choked if (m_num_invalid_requests > 300 && !m_peer_choked - && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) + && can_disconnect(error_code(errors::too_many_requests_when_choked + , get_libtorrent_category()))) { disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); return; @@ -3908,6 +3914,15 @@ namespace libtorrent if (m_disconnecting) return; + m_socket->set_close_reason(error_to_close_reason(ec)); + close_reason_t close_reason = (close_reason_t)m_socket->get_close_reason(); +#if defined TORRENT_LOGGING + if (close_reason != 0) + { + peer_log("*** CLOSE REASON [ %d ]", int(close_reason)); + } +#endif + // while being disconnected, it's possible that our torrent_peer // pointer gets cleared. Make sure we save it to be able to keep // proper books in the piece_picker (when debugging is enabled) @@ -4078,11 +4093,12 @@ namespace libtorrent t->alerts().post_alert( peer_error_alert(handle, remote(), pid(), op, ec)); } - else if (error <= 1 && t->alerts().should_post()) + + if (error <= 1 && t->alerts().should_post()) { t->alerts().post_alert( peer_disconnected_alert(handle, remote(), pid(), op - , m_socket->type(), ec)); + , m_socket->type(), ec, close_reason)); } } @@ -4657,7 +4673,7 @@ namespace libtorrent && d2 > time_limit && (m_ses.num_connections() >= m_settings.get_int(settings_pack::connections_limit) || (t && t->num_peers() >= t->max_connections())) - && can_disconnect(error_code(errors::timed_out_no_interest, get_libtorrent_category()))) + && can_disconnect(error_code(errors::timed_out_no_interest))) { #if defined TORRENT_LOGGING peer_log("*** MUTUAL NO INTEREST [ t1: %d t2: %d ]" @@ -6404,12 +6420,12 @@ namespace libtorrent if (m_settings.get_bool(settings_pack::close_redundant_connections) && !t->share_mode()) { bool ok_to_disconnect = - can_disconnect(error_code(errors::upload_upload_connection, get_libtorrent_category())) - || can_disconnect(error_code(errors::uninteresting_upload_peer, get_libtorrent_category())) - || can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category())) - || can_disconnect(error_code(errors::timed_out_no_interest, get_libtorrent_category())) - || can_disconnect(error_code(errors::timed_out_no_request, get_libtorrent_category())) - || can_disconnect(error_code(errors::timed_out_inactivity, get_libtorrent_category())); + can_disconnect(error_code(errors::upload_upload_connection)) + || can_disconnect(error_code(errors::uninteresting_upload_peer)) + || can_disconnect(error_code(errors::too_many_requests_when_choked)) + || can_disconnect(error_code(errors::timed_out_no_interest)) + || can_disconnect(error_code(errors::timed_out_no_request)) + || can_disconnect(error_code(errors::timed_out_inactivity)); // make sure upload only peers are disconnected if (t->is_upload_only() diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 65ab55fc9..1f1d10a44 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -2513,7 +2513,8 @@ retry: m_alerts.post_alert( peer_disconnected_alert(torrent_handle(), endp, peer_id() , op_bittorrent, s->type() - , error_code(errors::too_many_connections, get_libtorrent_category()))); + , error_code(errors::too_many_connections, get_libtorrent_category()) + , close_no_reason)); } #if defined TORRENT_LOGGING session_log("number of connections limit exceeded (conns: %d, limit: %d, slack: %d), connection rejected" diff --git a/src/socket_type.cpp b/src/socket_type.cpp index cb9ae07bb..2f2cd04c4 100644 --- a/src/socket_type.cpp +++ b/src/socket_type.cpp @@ -301,6 +301,36 @@ namespace libtorrent TORRENT_SOCKTYPE_FORWARD(close(ec)) } + void socket_type::set_close_reason(boost::uint16_t code) + { + switch (m_type) + { + case socket_type_int_impl::value: + get()->set_close_reason(code); + break; +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl >::value: + get >()->set_close_reason(boost::uint16_t code); + break; +#endif + default: break; + } + } + + boost::uint16_t socket_type::get_close_reason() + { + switch (m_type) + { + case socket_type_int_impl::value: + return get()->get_close_reason(); +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl >::value: + return get >()->get_close_reason(); +#endif + default: return 0; + } + } + socket_type::endpoint_type socket_type::local_endpoint(error_code& ec) const { TORRENT_SOCKTYPE_FORWARD_RET(local_endpoint(ec), socket_type::endpoint_type()) } diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp index 62c279462..c209394ad 100644 --- a/src/utp_stream.cpp +++ b/src/utp_stream.cpp @@ -252,6 +252,7 @@ struct utp_socket_impl , m_out_packets(0) , m_send_delay(0) , m_recv_delay(0) + , m_close_reason(0) , m_port(0) , m_send_id(send_id) , m_recv_id(recv_id) @@ -306,6 +307,7 @@ struct utp_socket_impl // returns true if there were handlers cancelled // if it returns false, we can detach immediately bool destroy(); + void set_close_reason(boost::uint16_t code); void detach(); void send_syn(); void send_fin(); @@ -318,8 +320,9 @@ struct utp_socket_impl bool send_pkt(int flags = 0); bool resend_packet(packet* p, bool fast_resend = false); void send_reset(utp_header* ph); - void parse_sack(boost::uint16_t packet_ack, boost::uint8_t const* ptr, int size, int* acked_bytes - , ptime const now, boost::uint32_t& min_rtt); + void parse_sack(boost::uint16_t packet_ack, boost::uint8_t const* ptr + , int size, int* acked_bytes, ptime const now, boost::uint32_t& min_rtt); + void parse_close_reason(boost::uint8_t const* ptr, int size); void write_payload(boost::uint8_t* ptr, int size); void maybe_inc_acked_seq_nr(); void ack_packet(packet* p, ptime const& receive_time @@ -514,6 +517,11 @@ public: // average RTT sliding_average<16> m_rtt; + // if this is != 0, it means the upper layer provided a reason for why + // the connection is being closed. The reason is indicated by this + // non-zero value which is included in a packet header extension + boost::uint16_t m_close_reason; + // port of destination endpoint boost::uint16_t m_port; @@ -789,6 +797,7 @@ int utp_stream::recv_delay() const utp_stream::utp_stream(asio::io_service& io_service) : m_io_service(io_service) , m_impl(0) + , m_incoming_close_reason(0) , m_open(false) { } @@ -798,6 +807,17 @@ utp_socket_impl* utp_stream::get_impl() return m_impl; } +void utp_stream::set_close_reason(boost::uint16_t code) +{ + if (!m_impl) return; + m_impl->set_close_reason(code); +} + +boost::uint16_t utp_stream::get_close_reason() +{ + return m_incoming_close_reason; +} + void utp_stream::close() { if (!m_impl) return; @@ -860,7 +880,15 @@ int utp_stream::read_buffer_size() const return m_impl->m_receive_buffer_size; } -void utp_stream::on_read(void* self, size_t bytes_transferred, error_code const& ec, bool kill) +void utp_stream::on_close_reason(void* self, boost::uint16_t close_reason) +{ + utp_stream* s = (utp_stream*)self; + TORRENT_ASSERT(s->m_impl); + s->m_incoming_close_reason = close_reason; +} + +void utp_stream::on_read(void* self, size_t bytes_transferred + , error_code const& ec, bool kill) { utp_stream* s = (utp_stream*)self; @@ -882,7 +910,8 @@ void utp_stream::on_read(void* self, size_t bytes_transferred, error_code const& // tmp(ec, bytes_transferred); } -void utp_stream::on_write(void* self, size_t bytes_transferred, error_code const& ec, bool kill) +void utp_stream::on_write(void* self, size_t bytes_transferred + , error_code const& ec, bool kill) { utp_stream* s = (utp_stream*)self; @@ -1227,12 +1256,22 @@ void utp_socket_impl::maybe_trigger_send_callback() m_write_buffer.clear(); } +void utp_socket_impl::set_close_reason(boost::uint16_t code) +{ +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: set_close_reason: %d\n" + , this, int(m_close_reason)); +#endif + m_close_reason = code; +} + bool utp_socket_impl::destroy() { INVARIANT_CHECK; #if TORRENT_UTP_LOG - UTP_LOGV("%8p: destroy state:%s\n", this, socket_state_names[m_state]); + UTP_LOGV("%8p: destroy state:%s (close-reason: %d)\n" + , this, socket_state_names[m_state], int(m_close_reason)); #endif if (m_userdata == 0) return false; @@ -1293,7 +1332,7 @@ void utp_socket_impl::send_syn() p->need_resend = false; utp_header* h = (utp_header*)p->buf; h->type_ver = (ST_SYN << 4) | 1; - h->extension = 0; + h->extension = utp_no_extension; // using recv_id here is intentional! This is an odd // thing in uTP. The syn packet is sent with the connection // ID that it expects to receive the syn ack on. All @@ -1392,7 +1431,7 @@ void utp_socket_impl::send_reset(utp_header* ph) utp_header h; h.type_ver = (ST_RESET << 4) | 1; - h.extension = 0; + h.extension = utp_no_extension; h.connection_id = m_send_id; h.timestamp_difference_microseconds = m_reply_micro; h.wnd_size = 0; @@ -1415,6 +1454,21 @@ std::size_t utp_socket_impl::available() const return m_receive_buffer_size; } +void utp_socket_impl::parse_close_reason(boost::uint8_t const* ptr, int size) +{ + if (size != 4) return; + // skip reserved bytes + ptr += 2; + boost::uint16_t incoming_close_reason = detail::read_uint16(ptr); + + UTP_LOGV("%8p: incoming close_reason: %d\n" + , this, int(incoming_close_reason)); + + if (m_userdata == 0) return; + + utp_stream::on_close_reason(m_userdata, incoming_close_reason); +} + void utp_socket_impl::parse_sack(boost::uint16_t packet_ack, boost::uint8_t const* ptr , int size, int* acked_bytes, ptime const now, boost::uint32_t& min_rtt) { @@ -1600,11 +1654,12 @@ void utp_socket_impl::remove_sack_header(packet* p) boost::uint8_t* ptr = p->buf + sizeof(utp_header); utp_header* h = (utp_header*)p->buf; - TORRENT_ASSERT(h->extension == 1); + TORRENT_ASSERT(h->extension == utp_sack); h->extension = ptr[0]; int sack_size = ptr[1]; - TORRENT_ASSERT(h->extension == 0); + TORRENT_ASSERT(h->extension == utp_no_extension + || h->extension == utp_close_reason); UTP_LOGV("%8p: removing SACK header, %d bytes\n" , this, sack_size + 2); @@ -1655,7 +1710,8 @@ bool utp_socket_impl::send_pkt(int flags) // first see if we need to resend any packets - // TODO: this loop may not be very efficient + // TODO: this loop is not very efficient. It could be fixed by having + // a separate list of sequence numbers that need resending for (int i = (m_acked_seq_nr + 1) & ACK_MASK; i != m_seq_nr; i = (i + 1) & ACK_MASK) { packet* p = (packet*)m_outbuf.at(i); @@ -1687,7 +1743,11 @@ bool utp_socket_impl::send_pkt(int flags) if (sack > 32) sack = 32; } - int header_size = sizeof(utp_header) + (sack ? sack + 2 : 0); + boost::uint32_t close_reason = m_close_reason; + + int header_size = sizeof(utp_header) + + (sack ? sack + 2 : 0) + + (close_reason ? 6 : 0); int payload_size = m_write_buffer_size; if (m_mtu - header_size < payload_size) payload_size = m_mtu - header_size; @@ -1798,7 +1858,8 @@ bool utp_socket_impl::send_pkt(int flags) h = (utp_header*)ptr; ptr += sizeof(utp_header); - h->extension = sack ? 1 : 0; + h->extension = sack ? utp_sack + : close_reason ? utp_close_reason : utp_no_extension; h->connection_id = m_send_id; // seq_nr is ignored for ST_STATE packets, so it doesn't // matter that we say this is a sequence number we haven't @@ -1819,7 +1880,7 @@ bool utp_socket_impl::send_pkt(int flags) // if the packet has a selective force header, we'll need // to update it - if (h->extension == 1) + if (h->extension == utp_sack) { sack = ptr[1]; // if we no longer have any out-of-order packets waiting @@ -1864,13 +1925,20 @@ bool utp_socket_impl::send_pkt(int flags) if (sack) { - *ptr++ = 0; // end of extension chain + *ptr++ = close_reason ? utp_close_reason : utp_no_extension; *ptr++ = sack; // bytes for SACK bitfield write_sack(ptr, sack); ptr += sack; TORRENT_ASSERT(ptr <= p->buf + p->header_size); } + if (close_reason) + { + *ptr++ = utp_no_extension; + *ptr++ = 4; + detail::write_uint32(close_reason, ptr); + } + if (m_bytes_in_flight > 0 && p->size < p->allocated && !force @@ -2114,7 +2182,7 @@ bool utp_socket_impl::resend_packet(packet* p, bool fast_resend) // if the packet has a selective ack header, we'll need // to update it - if (h->extension == 1 && h->ack_nr != m_ack_nr) + if (h->extension == utp_sack && h->ack_nr != m_ack_nr) { boost::uint8_t* ptr = p->buf + sizeof(utp_header); int sack_size = ptr[1]; @@ -2842,9 +2910,12 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size } switch(extension) { - case 1: // selective ACKs + case utp_sack: // selective ACKs parse_sack(ph->ack_nr, ptr, len, &acked_bytes, receive_time, min_rtt); break; + case utp_close_reason: + parse_close_reason(ptr, len); + break; } ptr += len; extension = next_extension;