/* Copyright (c) 2015, Steven Siloti 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 "test.hpp" #include "simulator/simulator.hpp" #include "libtorrent/aux_/session_listen_socket.hpp" #include "libtorrent/udp_socket.hpp" #include "libtorrent/kademlia/dht_tracker.hpp" #include "libtorrent/kademlia/dht_state.hpp" #include "libtorrent/performance_counters.hpp" #include "libtorrent/entry.hpp" #include "libtorrent/session_settings.hpp" #include "libtorrent/span.hpp" #include "libtorrent/kademlia/dht_observer.hpp" #include #include #include using namespace lt; using namespace sim; using namespace std::placeholders; #if !defined TORRENT_DISABLE_DHT struct obs : dht::dht_observer { void set_external_address(lt::aux::session_listen_socket*, address const& /* addr */ , address const& /* source */) override {} void get_peers(sha1_hash const&) override {} void outgoing_get_peers(sha1_hash const& /* target */ , sha1_hash const& /* sent_target */, udp::endpoint const& /* ep */) override {} void announce(sha1_hash const& /* ih */ , address const& /* addr */, int /* port */) override {} bool on_dht_request(string_view /* query */ , dht::msg const& /* request */, entry& /* response */) override { return false; } #ifndef TORRENT_DISABLE_LOGGING bool should_log(module_t) const override { return true; } void log(dht_logger::module_t l, char const* fmt, ...) override { va_list v; va_start(v, fmt); vprintf(fmt, v); va_end(v); puts("\n"); } void log_packet(message_direction_t /* dir */ , span /* pkt */ , udp::endpoint const& /* node */) override {} #endif }; struct mock_socket : lt::aux::session_listen_socket { address get_external_address() override { return get_local_endpoint().address(); } tcp::endpoint get_local_endpoint() override { return tcp::endpoint(address_v4::from_string("40.30.20.10"), 8888); } }; void send_packet(lt::udp_socket& sock, lt::aux::session_listen_socket*, udp::endpoint const& ep , span p, error_code& ec, int flags) { sock.send(ep, p, ec, flags); } #endif // #if !defined TORRENT_DISABLE_DHT TORRENT_TEST(dht_rate_limit) { #if !defined TORRENT_DISABLE_DHT default_config cfg; simulation sim(cfg); asio::io_service dht_ios(sim, address_v4::from_string("40.30.20.10")); // receiver (the DHT under test) lt::udp_socket sock(dht_ios); obs o; mock_socket ds; error_code ec; sock.bind(udp::endpoint(address_v4::from_string("40.30.20.10"), 8888), ec); dht_settings dhtsett; dhtsett.block_ratelimit = 100000; // disable the DOS blocker dhtsett.ignore_dark_internet = false; dhtsett.upload_rate_limit = 400; float const target_upload_rate = 400; int const num_packets = 2000; counters cnt; dht::dht_state state; std::unique_ptr dht_storage(dht::dht_default_storage_constructor(dhtsett)); auto dht = std::make_shared( &o, dht_ios, std::bind(&send_packet, std::ref(sock), _1, _2, _3, _4, _5) , dhtsett, cnt, *dht_storage, state); dht->new_socket(&ds); bool stop = false; std::function on_read = [&](error_code const& ec, size_t const /* bytes */) { if (ec) return; udp_socket::packet p; error_code err; int const num = int(sock.read(lt::span(&p, 1), err)); if (num) dht->incoming_packet(p.from, p.data); if (stop || err) return; sock.async_read(on_read); }; sock.async_read(on_read); // sender int num_packets_sent = 0; asio::io_service sender_ios(sim, address_v4::from_string("10.20.30.40")); udp::socket sender_sock(sender_ios); sender_sock.open(udp::v4()); sender_sock.bind(udp::endpoint(address_v4(), 4444)); sender_sock.io_control(udp::socket::non_blocking_io(true)); asio::high_resolution_timer timer(sender_ios); std::function sender_tick = [&](error_code const&) { if (num_packets_sent == num_packets) { // we're done. shut down (a second from now, to let the dust settle) timer.expires_from_now(chrono::seconds(1)); timer.async_wait([&](error_code const&) { dht->stop(); stop = true; sender_sock.close(); sock.close(); }); return; } char const packet[] = "d1:ad2:id20:ababababababababababe1:y1:q1:q4:pinge"; sender_sock.send_to(asio::const_buffers_1(packet, sizeof(packet)-1) , udp::endpoint(address_v4::from_string("40.30.20.10"), 8888)); ++num_packets_sent; timer.expires_from_now(chrono::milliseconds(10)); timer.async_wait(sender_tick); }; timer.expires_from_now(chrono::milliseconds(10)); timer.async_wait(sender_tick); udp::endpoint from; int num_bytes_received = 0; int num_packets_received = 0; char buffer[1500]; std::function on_receive = [&](error_code const& ec, std::size_t const bytes) { if (ec) return; num_bytes_received += int(bytes); ++num_packets_received; sender_sock.async_receive_from(asio::mutable_buffers_1(buffer, sizeof(buffer)) , from, on_receive); }; sender_sock.async_receive_from(asio::mutable_buffers_1(buffer, sizeof(buffer)) , from, on_receive); // run simulation lt::clock_type::time_point start = lt::clock_type::now(); sim.run(); lt::clock_type::time_point end = lt::clock_type::now(); // subtract one target_upload_rate here, since we initialize the quota to one // full second worth of bandwidth float const average_upload_rate = (num_bytes_received - target_upload_rate) / (duration_cast(end - start).count() * 0.001f); std::printf("send %d packets. received %d packets (%d bytes). average rate: %f (target: %f)\n" , num_packets_sent, num_packets_received, num_bytes_received , average_upload_rate, target_upload_rate); // the actual upload rate should be within 5% of the target TEST_CHECK(std::abs(average_upload_rate - target_upload_rate) < target_upload_rate * 0.05); TEST_EQUAL(cnt[counters::dht_messages_in], num_packets); // the number of dropped packets + the number of received pings, should equal // exactly the number of packets we sent TEST_EQUAL(cnt[counters::dht_messages_in_dropped] + cnt[counters::dht_ping_in], num_packets); #endif // #if !defined TORRENT_DISABLE_EXTENSIONS && !defined TORRENT_DISABLE_DHT }