diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index 8eb137311..1204eef6f 100755 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -66,6 +66,11 @@ namespace libtorrent struct session_impl; } + struct protocol_error: std::runtime_error + { + protocol_error(const std::string& msg): std::runtime_error(msg) {}; + }; + struct peer_request { int piece; @@ -181,13 +186,28 @@ namespace libtorrent // peer. int send_quota() const { return m_send_quota; } + void received_valid_data() + { + m_trust_points++; + if (m_trust_points > 20) m_trust_points = 20; + } + + void received_invalid_data() + { + m_trust_points--; + if (m_trust_points < 5) m_trust_points = 5; + } + + int trust_points() const + { return m_trust_points; } + #ifndef NDEBUG boost::shared_ptr m_logger; #endif private: - bool dispatch_message(); + void dispatch_message(); void send_buffer_updated(); void send_bitfield(); @@ -314,6 +334,14 @@ namespace libtorrent // speed int m_send_quota; int m_send_quota_left; + + // for every valid piece we receive where this + // peer was one of the participants, we increase + // this value. For every invalid piece we receive + // where this peer was a participant, we decrease + // this value. If it sinks below a threshold, its + // considered a bad peer and will be banned. + int m_trust_points; }; // this is called each time this peer generates some diff --git a/include/libtorrent/policy.hpp b/include/libtorrent/policy.hpp index e0f4d8663..bfb129889 100755 --- a/include/libtorrent/policy.hpp +++ b/include/libtorrent/policy.hpp @@ -62,7 +62,8 @@ namespace libtorrent void pulse(); // called when an incoming connection is accepted - void new_connection(const boost::weak_ptr& c); + // return false if the connection closed + bool new_connection(const boost::weak_ptr& c); // this is called once for every peer we get from // the tracker @@ -71,6 +72,10 @@ namespace libtorrent // the given connection was just closed void connection_closed(const peer_connection& c); + // is called when a peer is believed to have + // sent invalid data + void ban_peer(const peer_connection& c); + // the peer has got at least one interesting piece void peer_is_interesting(peer_connection& c); @@ -105,6 +110,7 @@ namespace libtorrent , optimistic_unchokes(0) , prev_amount_upload(0) , prev_amount_download(0) + , banned(false) {} bool operator==(const peer_id& pid) const @@ -140,6 +146,9 @@ namespace libtorrent int prev_amount_upload; int prev_amount_download; + // is set to true if this peer has been banned + bool banned; + // if the peer is connected now, this // will refer to a valid peer_connection boost::weak_ptr connection; diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 64b9e883c..42508094d 100755 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -216,6 +216,11 @@ namespace libtorrent piece_picker& picker() { return m_picker; } + // this is called from the peer_connection + // each time a piece has failed the hash + // test + void piece_failed(int index); + // DEBUG #ifndef NDEBUG logger* spawn_logger(const char* title); diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 2369fb634..e02188a60 100755 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -225,11 +225,11 @@ void libtorrent::peer_connection::send_handshake() send_buffer_updated(); } -bool libtorrent::peer_connection::dispatch_message() +void libtorrent::peer_connection::dispatch_message() { int packet_type = m_recv_buffer[0]; if (packet_type > 8 || packet_type < 0) - return false; + throw protocol_error("unknown message id"); switch (packet_type) { @@ -295,7 +295,7 @@ bool libtorrent::peer_connection::dispatch_message() std::size_t index = read_int(&m_recv_buffer[1]); // if we got an invalid message, abort if (index >= m_have_piece.size()) - return false; + throw protocol_error("have message with higher index than the number of pieces"); #ifndef NDEBUG (*m_logger) << m_socket->sender().as_string() << " <== HAVE [ piece: " << index << "]\n"; @@ -323,7 +323,7 @@ bool libtorrent::peer_connection::dispatch_message() case msg_bitfield: { if (m_packet_size - 1 != (m_have_piece.size() + 7) / 8) - return false; + throw protocol_error("bitfield with invalid size"); #ifndef NDEBUG (*m_logger) << m_socket->sender().as_string() << " <== BITFIELD\n"; @@ -390,7 +390,7 @@ bool libtorrent::peer_connection::dispatch_message() #ifndef NDEBUG (*m_logger) << m_socket->sender().as_string() << " piece index invalid\n"; #endif - return false; + throw protocol_error("invalid piece index in piece message"); } int offset = read_int(&m_recv_buffer[5]); int len = m_packet_size - 9; @@ -400,7 +400,7 @@ bool libtorrent::peer_connection::dispatch_message() #ifndef NDEBUG (*m_logger) << m_socket->sender().as_string() << " offset < 0\n"; #endif - return false; + throw protocol_error("offset < 0 in piece message"); } if (offset + len > m_torrent->torrent_file().piece_size(index)) @@ -408,7 +408,7 @@ bool libtorrent::peer_connection::dispatch_message() #ifndef NDEBUG (*m_logger) << m_socket->sender().as_string() << " piece packet contains more data than the piece size\n"; #endif - return false; + throw protocol_error("piece message contains more data than the piece size"); } if (offset % m_torrent->block_size() != 0) @@ -416,7 +416,7 @@ bool libtorrent::peer_connection::dispatch_message() #ifndef NDEBUG (*m_logger) << m_socket->sender().as_string() << " piece packet contains unaligned offset\n"; #endif - return false; + throw protocol_error("piece message contains unaligned offset"); } /* piece_block req = m_download_queue.front(); @@ -471,6 +471,8 @@ bool libtorrent::peer_connection::dispatch_message() picker.mark_as_finished(block_finished, m_peer_id); + m_torrent->get_policy().block_finished(*this, block_finished); + // did we just finish the piece? if (picker.is_piece_finished(index)) { @@ -483,26 +485,10 @@ bool libtorrent::peer_connection::dispatch_message() } else { -#ifndef NDEBUG - std::cout << "hash-test failed. Some of these peers sent invalid data:\n"; - std::vector downloaders; - picker.get_downloaders(downloaders, index); - std::copy(downloaders.begin(), downloaders.end(), std::ostream_iterator(std::cout, "\n")); -#endif - // we have to let the piece_picker know that - // this piece failed the check as it can restore it - // and mark it as being interesting for download - // TODO: do this more intelligently! and keep track - // of how much crap (data that failed hash-check) and - // how much redundant data we have downloaded - // if some clients has sent more than one piece - // start with redownloading the pieces that the client - // that has sent the least number of pieces - picker.restore_piece(index); + m_torrent->piece_failed(index); } m_torrent->get_policy().piece_finished(*this, index, verified); } - m_torrent->get_policy().block_finished(*this, block_finished); break; } @@ -534,8 +520,6 @@ bool libtorrent::peer_connection::dispatch_message() break; } } - - return true; } void libtorrent::peer_connection::cancel_block(piece_block block) @@ -932,14 +916,9 @@ void libtorrent::peer_connection::receive_data() case read_packet: - if (!dispatch_message()) - { - #ifndef NDEBUG - (*m_logger) << m_socket->sender().as_string() << " received invalid packet\n"; - #endif - // invalid message - throw network_error(0); - } + // TODO: dispatch should throw instead of returning status + // and instead of throwing network_error, throw protocol_error + dispatch_message(); m_state = read_packet_size; m_packet_size = 4; diff --git a/src/policy.cpp b/src/policy.cpp index db72d29bd..23bae5b0b 100755 --- a/src/policy.cpp +++ b/src/policy.cpp @@ -197,9 +197,11 @@ namespace libtorrent void peer_connection::request_piece(int index); const std::vector& peer_connection::download_queue(); - TODO: to implement choking/unchoking we need a list with all - connected peers. Something like this: + TODO: implement a limit of the number of unchoked peers. + TODO: implement some kind of limit of the number of sockets + opened, to use for systems where a user has a limited number + of open file descriptors */ @@ -238,6 +240,10 @@ namespace libtorrent else if (uploaded - downloaded <= m_torrent->block_size() && c->is_choked() && c->is_peer_interested()) { + // TODO: if we're not interested in this peer + // we should only unchoke it if it' its turn + // to be optimistically unchoked. + // we have catched up. We have now shared the same amount // to eachother. Unchoke this peer. c->unchoke(); @@ -245,11 +251,19 @@ namespace libtorrent } } - void policy::new_connection(const boost::weak_ptr& c) + void policy::ban_peer(const peer_connection& c) + { + std::vector::iterator i = std::find(m_peers.begin(), m_peers.end(), c.get_peer_id()); + assert(i != m_peers.end()); + + i->banned = true; + } + + bool policy::new_connection(const boost::weak_ptr& c) { boost::shared_ptr con = c.lock(); assert(con.get() != 0); - if (con.get() == 0) return; + if (con.get() == 0) return false; std::vector::iterator i = std::find(m_peers.begin(), m_peers.end(), con->get_peer_id()); @@ -264,10 +278,12 @@ namespace libtorrent else { assert(i->connection.expired()); + if (i->banned) return false; } i->connected = boost::posix_time::second_clock::local_time(); i->connection = c; + return true; } void policy::peer_from_tracker(const address& remote, const peer_id& id) @@ -291,11 +307,14 @@ namespace libtorrent return; } + if (i->banned) return; + i->connected = boost::posix_time::second_clock::local_time(); i->connection = m_torrent->connect_to_peer(remote, id); } catch(network_error&) {} + catch(protocol_error&) {} } // this is called when we are choked by a peer diff --git a/src/session.cpp b/src/session.cpp index 09d252d6d..f88f11bf2 100755 --- a/src/session.cpp +++ b/src/session.cpp @@ -190,7 +190,7 @@ namespace libtorrent { listener->listen(m_listen_port, 5); } - catch(network_error&) + catch(std::exception&) { if (m_listen_port > max_port) throw; @@ -299,7 +299,7 @@ namespace libtorrent // (*m_logger) << "readable: " << p->first->sender().as_string() << "\n"; p->second->receive_data(); } - catch(network_error&) + catch(std::exception&) { // the connection wants to disconnect for some reason, remove it // from the connection-list @@ -338,7 +338,7 @@ namespace libtorrent // (*m_logger) << "writable: " << p->first->sender().as_string() << "\n"; p->second->send_data(); } - catch(network_error&) + catch(std::exception&) { // the connection wants to disconnect for some reason, remove it // from the connection-list diff --git a/src/storage.cpp b/src/storage.cpp index b035161b1..f0822829b 100755 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -366,6 +366,7 @@ int libtorrent::piece_file::read(char* buf, int size, bool lock_) int read_bytes = left_to_read; if (m_file_offset + read_bytes > m_file_iter->size) read_bytes = m_file_iter->size - m_file_offset; + assert(read_bytes > 0); m_file.read(buf + buf_pos, read_bytes); @@ -505,7 +506,10 @@ void libtorrent::piece_file::write(const char* buf, int size, bool lock_) { int write_bytes = left_to_write; if (m_file_offset + write_bytes > m_file_iter->size) + { + assert(m_file_iter->size > m_file_offset); write_bytes = m_file_iter->size - m_file_offset; + } assert(buf_pos >= 0); assert(write_bytes > 0); @@ -515,6 +519,7 @@ void libtorrent::piece_file::write(const char* buf, int size, bool lock_) buf_pos += write_bytes; assert(buf_pos >= 0); m_file_offset += write_bytes; + assert(m_file_offset < m_file_iter->size); m_piece_offset += write_bytes; if (left_to_write > 0) @@ -522,6 +527,7 @@ void libtorrent::piece_file::write(const char* buf, int size, bool lock_) ++m_file_iter; assert(m_file_iter != m_storage->m_torrent_file->end_files()); + assert(m_file_iter->size > m_file_offset); boost::filesystem::path path = m_storage->m_save_path / m_file_iter->path / m_file_iter->filename; diff --git a/src/torrent.cpp b/src/torrent.cpp index 699782afe..b5d826ce2 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -240,8 +240,69 @@ namespace libtorrent != m_connections.end(); } + void torrent::piece_failed(int index) + { + std::vector downloaders; + m_picker.get_downloaders(downloaders, index); + +#ifndef NDEBUG + std::cout << "hash-test failed. Some of these peers sent invalid data:\n"; + std::copy(downloaders.begin(), downloaders.end(), std::ostream_iterator(std::cout, "\n")); +#endif + + // decrease the trust point of all peers that sent + // parts of this piece. + // TODO: implement this loop more efficient + for (std::vector::iterator i = m_connections.begin(); + i != m_connections.end(); + ++i) + { + if (std::find(downloaders.begin(), downloaders.end(), (*i)->get_peer_id()) + != downloaders.end()) + { + (*i)->received_invalid_data(); + if ((*i)->trust_points() <= -5) + { + // we don't trust this peer anymore + // ban it. + m_policy->ban_peer(*(*i)); + } + } + } + + // we have to let the piece_picker know that + // this piece failed the check as it can restore it + // and mark it as being interesting for download + // TODO: do this more intelligently! and keep track + // of how much crap (data that failed hash-check) and + // how much redundant data we have downloaded + // if some clients has sent more than one piece + // start with redownloading the pieces that the client + // that has sent the least number of pieces + m_picker.restore_piece(index); + } + + void torrent::announce_piece(int index) { + std::vector downloaders; + m_picker.get_downloaders(downloaders, index); + + // increase the trust point of all peers that sent + // parts of this piece. + // TODO: implement this loop more efficient + for (std::vector::iterator i = m_connections.begin(); + i != m_connections.end(); + ++i) + { + if (std::find(downloaders.begin(), downloaders.end(), (*i)->get_peer_id()) + != downloaders.end()) + { + (*i)->received_valid_data(); + } + } + + m_picker.we_have(index); for (std::vector::iterator i = m_connections.begin(); i != m_connections.end(); ++i) (*i)->announce_piece(index); @@ -378,7 +439,7 @@ namespace libtorrent = m_ses->m_connections.find(p->get_socket()); assert(i != m_ses->m_connections.end()); - m_policy->new_connection(i->second); + if (!m_policy->new_connection(i->second)) throw network_error(0); } void torrent::close_all_connections() diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 0388f54ae..493c39778 100755 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -156,6 +156,11 @@ namespace libtorrent ++i) { peer_connection* peer = *i; + + // peers that hasn't finished the handshake should + // not be included in this list + if (peer->associated_torrent() == 0) continue; + v.push_back(peer_info()); peer_info& p = v.back();