diff --git a/doc/rtorrent.1.xml b/doc/rtorrent.1.xml
index ccea8875..9c455859 100644
--- a/doc/rtorrent.1.xml
+++ b/doc/rtorrent.1.xml
@@ -677,6 +677,29 @@ setting 0.
+
+ throttle_up = name, upload_rate
+ throttle_down = name, download_rate
+
+Define secondary throttle and/or set the given upload or download rate. Attach to a download with the d.set_throttle_name=name command
+or switch throttles with Ctrl-T. Download must be stopped when changing throttles. Note that secondary throttles only work if the
+global upload/download is throttled. Setting a download to use the NULL throttle makes the download unthrottled
+even when there is a global throttle. Note that this special case bypasses the global throttle entirely, and as such its rate and
+transfer amounts are not included in the global statistics.
+
+
+
+
+ throttle_ip = name, host
+ throttle_ip = name, network/prefix
+ throttle_ip = name, start, end
+
+Use the given secondary throttle for a host, CIDR network or IP range. All peers with a matching IP will use this throttle instead
+of the global throttle or a custom download throttle. The name may be NULL to make these peers unthrottled, with
+the same caveats as explained above.
+
+
+
diff --git a/src/command_download.cc b/src/command_download.cc
index b9816c42..de40114d 100644
--- a/src/command_download.cc
+++ b/src/command_download.cc
@@ -44,6 +44,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -667,6 +668,9 @@ initialize_command_download() {
ADD_CD_VALUE_MEM_UNI("skip_rate", &torrent::Download::mutable_skip_rate, &torrent::Rate::rate);
ADD_CD_VALUE_MEM_UNI("skip_total", &torrent::Download::mutable_skip_rate, &torrent::Rate::total);
+ ADD_CD_STRING("set_throttle_name", std::mem_fun(&core::Download::set_throttle_name));
+ ADD_CD_SLOT_PUBLIC("d.get_throttle_name", call_unknown, rpc::get_variable_d_fn("rtorrent", "throttle_name"), "i:", "");
+
ADD_CD_VALUE_UNI("creation_date", rak::on(std::mem_fun(&core::Download::download), std::mem_fun(&torrent::Download::creation_date)));
ADD_CD_VALUE_UNI("bytes_done", rak::on(std::mem_fun(&core::Download::download), std::mem_fun(&torrent::Download::bytes_done)));
ADD_CD_VALUE_UNI("ratio", std::ptr_fun(&retrieve_d_ratio));
diff --git a/src/command_network.cc b/src/command_network.cc
index 09e4915d..cce13d36 100644
--- a/src/command_network.cc
+++ b/src/command_network.cc
@@ -61,6 +61,120 @@
#include "control.h"
#include "command_helpers.h"
+torrent::Object
+apply_throttle(bool up, const torrent::Object& rawArgs) {
+ const torrent::Object::list_type& args = rawArgs.as_list();
+ torrent::Object::list_const_iterator argItr = args.begin();
+
+ const std::string& name = argItr->as_string();
+ if (name.empty() || name == "NULL")
+ throw torrent::input_error("Invalid throttle name.");
+
+ if ((++argItr)->as_string().empty())
+ return torrent::Object();
+
+ int64_t rate;
+ rpc::parse_whole_value_nothrow(argItr->as_string().c_str(), &rate);
+
+ if (rate < 0)
+ throw torrent::input_error("Throttle rate must be non-negative.");
+
+ core::ThrottleMap::iterator itr = control->core()->throttles().find(name);
+ if (itr == control->core()->throttles().end())
+ itr = control->core()->throttles().insert(std::make_pair(name, torrent::ThrottlePair(NULL, NULL))).first;
+
+ torrent::Throttle*& throttle = up ? itr->second.first : itr->second.second;
+ if (rate != 0 && throttle == NULL)
+ throttle = (up ? torrent::up_throttle_global() : torrent::down_throttle_global())->create_slave();
+
+ if (throttle != NULL)
+ throttle->set_max_rate(rate * 1024);
+
+ return torrent::Object();
+}
+
+static const int throttle_info_up = (1 << 0);
+static const int throttle_info_down = (1 << 1);
+static const int throttle_info_max = (1 << 2);
+static const int throttle_info_rate = (1 << 3);
+
+torrent::Object
+retrieve_throttle_info(int flags, const torrent::Object& rawArgs) {
+ const std::string& name = rawArgs.as_string();
+ core::ThrottleMap::iterator itr = control->core()->throttles().find(name);
+ torrent::ThrottlePair throttles = itr == control->core()->throttles().end() ? torrent::ThrottlePair(NULL, NULL) : itr->second;
+ torrent::Throttle* throttle = flags & throttle_info_down ? throttles.second : throttles.first;
+ torrent::Throttle* global = flags & throttle_info_down ? torrent::down_throttle_global() : torrent::up_throttle_global();
+
+ if (throttle == NULL && name.empty())
+ throttle = global;
+
+ if (throttle == NULL)
+ return flags & throttle_info_rate ? (int64_t)0 : (int64_t)-1;
+ else if (!throttle->is_throttled() || !global->is_throttled())
+ return (int64_t)0;
+ else if (flags & throttle_info_rate)
+ return (int64_t)throttle->rate()->rate();
+ else
+ return (int64_t)throttle->max_rate();
+}
+
+std::pair
+parse_address_range(const torrent::Object::list_type& args, torrent::Object::list_type::const_iterator itr) {
+ unsigned int prefixWidth, ret;
+ char dummy;
+ char host[1024];
+ rak::address_info* ai;
+
+ ret = std::sscanf(itr->as_string().c_str(), "%1023[^/]/%d%c", host, &prefixWidth, &dummy);
+ if (ret < 1 || rak::address_info::get_address_info(host, PF_INET, SOCK_STREAM, &ai) != 0)
+ throw torrent::input_error("Could not resolve host.");
+
+ uint32_t begin, end;
+ rak::socket_address sa;
+ sa.copy(*ai->address(), ai->length());
+ begin = end = sa.sa_inet()->address_h();
+ rak::address_info::free_address_info(ai);
+
+ if (ret == 2) {
+ if (++itr != args.end())
+ throw torrent::input_error("Cannot specify both network and range end.");
+
+ uint32_t netmask = std::numeric_limits::max() << (32 - prefixWidth);
+ if (prefixWidth >= 32 || sa.sa_inet()->address_h() & ~netmask)
+ throw torrent::input_error("Invalid address/prefix.");
+
+ end = sa.sa_inet()->address_h() | ~netmask;
+
+ } else if (++itr != args.end()) {
+ if (rak::address_info::get_address_info(itr->as_string().c_str(), PF_INET, SOCK_STREAM, &ai) != 0)
+ throw torrent::input_error("Could not resolve host.");
+
+ sa.copy(*ai->address(), ai->length());
+ rak::address_info::free_address_info(ai);
+ end = sa.sa_inet()->address_h();
+ }
+
+ // convert to [begin, end) making sure the end doesn't overflow
+ // (this precludes 255.255.255.255 from ever matching, but that's not a real IP anyway)
+ return std::make_pair(begin, std::max(end, end + 1));
+}
+
+torrent::Object
+apply_address_throttle(const torrent::Object& rawArgs) {
+ const torrent::Object::list_type& args = rawArgs.as_list();
+ if (args.size() < 2 || args.size() > 3)
+ throw torrent::input_error("Incorrect number of arguments.");
+
+ std::pair range = parse_address_range(args, ++args.begin());
+ core::ThrottleMap::iterator throttleItr = control->core()->throttles().find(args.begin()->as_string().c_str());
+ if (throttleItr == control->core()->throttles().end())
+ throw torrent::input_error("Throttle not found.");
+
+ control->core()->set_address_throttle(range.first, range.second, throttleItr->second);
+ return torrent::Object();
+}
+
torrent::Object
apply_encryption(const torrent::Object& rawArgs) {
const torrent::Object::list_type& args = rawArgs.as_list();
@@ -328,6 +442,15 @@ initialize_command_network() {
ADD_VARIABLE_VALUE("tracker_numwant", -1);
+ ADD_COMMAND_LIST("throttle_up", rak::bind_ptr_fn(&apply_throttle, true));
+ ADD_COMMAND_LIST("throttle_down", rak::bind_ptr_fn(&apply_throttle, false));
+ ADD_COMMAND_LIST("throttle_ip", rak::ptr_fn(&apply_address_throttle));
+
+ ADD_COMMAND_STRING("get_throttle_up_max", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_up | throttle_info_max));
+ ADD_COMMAND_STRING("get_throttle_up_rate", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_up | throttle_info_rate));
+ ADD_COMMAND_STRING("get_throttle_down_max", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_down | throttle_info_max));
+ ADD_COMMAND_STRING("get_throttle_down_rate", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_down | throttle_info_rate));
+
ADD_COMMAND_LIST("encryption", rak::ptr_fn(&apply_encryption));
ADD_COMMAND_STRING("tos", rak::ptr_fn(&apply_tos));
@@ -363,6 +486,7 @@ initialize_command_network() {
ADD_COMMAND_STRING_UN("dht", rak::make_mem_fun(control->dht_manager(), &core::DhtManager::set_start));
ADD_COMMAND_STRING_UN("dht_add_node", std::ptr_fun(&apply_dht_add_node));
ADD_COMMAND_VOID("dht_statistics", rak::make_mem_fun(control->dht_manager(), &core::DhtManager::dht_statistics));
+ ADD_COMMAND_STRING_TRI("dht_throttle", rak::make_mem_fun(control->dht_manager(), &core::DhtManager::set_throttle_name), rak::make_mem_fun(control->dht_manager(), &core::DhtManager::throttle_name));
ADD_VARIABLE_BOOL("peer_exchange", true);
diff --git a/src/command_ui.cc b/src/command_ui.cc
index fa4653ec..9d393601 100644
--- a/src/command_ui.cc
+++ b/src/command_ui.cc
@@ -315,6 +315,19 @@ apply_to_xb(const torrent::Object& rawArgs) {
return std::string(buffer);
}
+torrent::Object
+apply_to_throttle(const torrent::Object& rawArgs) {
+ int64_t arg = rawArgs.as_value();
+ if (arg < 0)
+ return "---";
+ else if (arg == 0)
+ return "off";
+
+ char buffer[32];
+ snprintf(buffer, 32, "%3d", (int)(arg / (1 << 10)));
+ return std::string(buffer);
+}
+
// A series of if/else statements. Every even arguments are
// conditionals and odd arguments are branches to be executed, except
// the last one which is always a branch.
@@ -503,4 +516,5 @@ initialize_command_ui() {
ADD_COMMAND_VALUE("to_kb", rak::ptr_fn(&apply_to_kb));
ADD_COMMAND_VALUE("to_mb", rak::ptr_fn(&apply_to_mb));
ADD_COMMAND_VALUE("to_xb", rak::ptr_fn(&apply_to_xb));
+ ADD_COMMAND_VALUE("to_throttle", rak::ptr_fn(&apply_to_throttle));
}
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
index 884356bb..fe07cbfe 100644
--- a/src/core/Makefile.am
+++ b/src/core/Makefile.am
@@ -32,6 +32,7 @@ libsub_core_a_SOURCES = \
poll_manager_kqueue.h \
poll_manager_select.cc \
poll_manager_select.h \
+ range_map.h \
view.cc \
view.h \
view_manager.cc \
diff --git a/src/core/dht_manager.cc b/src/core/dht_manager.cc
index 9a76a8fc..5f68f566 100644
--- a/src/core/dht_manager.cc
+++ b/src/core/dht_manager.cc
@@ -95,6 +95,10 @@ DhtManager::start_dht() {
if (!torrent::dht_manager()->is_valid() || torrent::dht_manager()->is_active())
return;
+ torrent::ThrottlePair throttles = control->core()->get_throttle(m_throttleName);
+ torrent::dht_manager()->set_upload_throttle(throttles.first);
+ torrent::dht_manager()->set_download_throttle(throttles.second);
+
int port = rpc::call_command_value("get_dht_port");
if (port <= 0)
return;
@@ -283,6 +287,7 @@ DhtManager::dht_statistics() {
dhtStats.insert_key("dht", dht_settings[m_start]);
dhtStats.insert_key("active", torrent::dht_manager()->is_active());
+ dhtStats.insert_key("throttle", m_throttleName);
if (torrent::dht_manager()->is_active()) {
torrent::DhtManager::statistics_type stats = torrent::dht_manager()->get_statistics();
@@ -303,4 +308,12 @@ DhtManager::dht_statistics() {
return dhtStats;
}
+void
+DhtManager::set_throttle_name(const std::string& throttleName) {
+ if (torrent::dht_manager()->is_active())
+ throw torrent::input_error("Cannot set DHT throttle while active.");
+
+ m_throttleName = throttleName;
+}
+
}
diff --git a/src/core/dht_manager.h b/src/core/dht_manager.h
index 076e6f90..093c54bc 100644
--- a/src/core/dht_manager.h
+++ b/src/core/dht_manager.h
@@ -58,6 +58,9 @@ public:
void set_start(const std::string& arg);
+ void set_throttle_name(const std::string& throttleName);
+ const std::string& throttle_name() const { return m_throttleName; }
+
private:
static const int dht_disable = 0;
static const int dht_off = 1;
@@ -82,6 +85,7 @@ private:
bool m_warned;
int m_start;
+ std::string m_throttleName;
};
}
diff --git a/src/core/download.cc b/src/core/download.cc
index 0a2254bb..0e2185b0 100644
--- a/src/core/download.cc
+++ b/src/core/download.cc
@@ -155,6 +155,18 @@ Download::receive_chunk_failed(__UNUSED uint32_t idx) {
m_chunksFailed++;
}
+void
+Download::set_throttle_name(const std::string& throttleName) {
+ if (m_download.is_active())
+ throw torrent::input_error("Cannot set throttle on active download.");
+
+ torrent::ThrottlePair throttles = control->core()->get_throttle(throttleName);
+ m_download.set_upload_throttle(throttles.first);
+ m_download.set_download_throttle(throttles.second);
+
+ m_download.bencode()->get_key("rtorrent").insert_key("throttle_name", throttleName);
+}
+
void
Download::set_root_directory(const std::string& path) {
torrent::FileList* fileList = m_download.file_list();
diff --git a/src/core/download.h b/src/core/download.h
index 64d081ec..20a131db 100644
--- a/src/core/download.h
+++ b/src/core/download.h
@@ -117,6 +117,8 @@ public:
void set_root_directory(const std::string& path);
+ void set_throttle_name(const std::string& throttleName);
+
bool operator == (const std::string& str) const;
float distributed_copies() const;
diff --git a/src/core/download_factory.cc b/src/core/download_factory.cc
index 25590882..e2d8ee88 100644
--- a/src/core/download_factory.cc
+++ b/src/core/download_factory.cc
@@ -336,6 +336,10 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre
download->download()->set_chunks_done(std::min(rtorrent->get_key_value("chunks_done"),
download->download()->file_list()->size_chunks()));
+ download->set_throttle_name(rtorrent->has_key_string("throttle_name")
+ ? rtorrent->get_key_string("throttle_name")
+ : std::string());
+
rtorrent->insert_preserve_copy("ignore_commands", (int64_t)0);
rtorrent->insert_preserve_copy("views", torrent::Object::create_list());
diff --git a/src/core/manager.cc b/src/core/manager.cc
index 41991d28..3886c4fa 100644
--- a/src/core/manager.cc
+++ b/src/core/manager.cc
@@ -54,6 +54,7 @@
#include
#include
#include
+#include
#include "rpc/parse_commands.h"
#include "utils/directory.h"
@@ -166,9 +167,14 @@ Manager::Manager() :
m_fileStatusCache = new FileStatusCache();
m_httpQueue = new HttpQueue();
m_httpStack = new CurlStack();
+
+ torrent::Throttle* unthrottled = torrent::Throttle::create_throttle();
+ unthrottled->set_max_rate(0);
+ m_throttles["NULL"] = std::make_pair(unthrottled, unthrottled);
}
Manager::~Manager() {
+ torrent::Throttle::destroy_throttle(m_throttles["NULL"].first);
delete m_downloadList;
delete m_downloadStore;
@@ -185,6 +191,31 @@ Manager::set_hashing_view(View* v) {
v->signal_changed().connect(sigc::mem_fun(this, &Manager::receive_hashing_changed));
}
+torrent::ThrottlePair
+Manager::get_throttle(const std::string& name) {
+ ThrottleMap::const_iterator itr = m_throttles.find(name);
+ torrent::ThrottlePair throttles = (itr == m_throttles.end() ? torrent::ThrottlePair(NULL, NULL) : itr->second);
+
+ if (throttles.first == NULL)
+ throttles.first = torrent::up_throttle_global();
+
+ if (throttles.second == NULL)
+ throttles.second = torrent::down_throttle_global();
+
+ return throttles;
+}
+
+void
+Manager::set_address_throttle(uint32_t begin, uint32_t end, torrent::ThrottlePair throttles) {
+ m_addressThrottles.set_merge(begin, end, throttles);
+ torrent::connection_manager()->set_address_throttle(sigc::mem_fun(control->core(), &core::Manager::get_address_throttle));
+}
+
+torrent::ThrottlePair
+Manager::get_address_throttle(const sockaddr* addr) {
+ return m_addressThrottles.get(rak::socket_address::cast_from(addr)->sa_inet()->address_h(), torrent::ThrottlePair(NULL, NULL));
+}
+
void
Manager::initialize_first() {
const char* poll = getenv("RTORRENT_POLL");
diff --git a/src/core/manager.h b/src/core/manager.h
index 43c10156..595f4a4b 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -40,8 +40,11 @@
#include
#include
+#include
+
#include "download_list.h"
#include "poll_manager.h"
+#include "range_map.h"
#include "log.h"
namespace torrent {
@@ -57,6 +60,8 @@ namespace core {
class DownloadStore;
class HttpQueue;
+typedef std::map ThrottleMap;
+
class View;
class Manager {
@@ -84,6 +89,13 @@ public:
Log& get_log_important() { return m_logImportant; }
Log& get_log_complete() { return m_logComplete; }
+ ThrottleMap& throttles() { return m_throttles; }
+ torrent::ThrottlePair get_throttle(const std::string& name);
+
+ // Use custom throttle for the given range of IP addresses.
+ void set_address_throttle(uint32_t begin, uint32_t end, torrent::ThrottlePair throttles);
+ torrent::ThrottlePair get_address_throttle(const sockaddr* addr);
+
// Really should find a more descriptive name.
void initialize_first();
void initialize_second();
@@ -120,6 +132,8 @@ public:
void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type());
private:
+ typedef RangeMap AddressThrottleMap;
+
void create_http(const std::string& uri);
void create_final(std::istream* s);
@@ -136,6 +150,9 @@ private:
View* m_hashingView;
+ ThrottleMap m_throttles;
+ AddressThrottleMap m_addressThrottles;
+
PollManager* m_pollManager;
Log m_logImportant;
Log m_logComplete;
diff --git a/src/core/range_map.h b/src/core/range_map.h
new file mode 100644
index 00000000..1f1bf8fc
--- /dev/null
+++ b/src/core/range_map.h
@@ -0,0 +1,194 @@
+// rTorrent - BitTorrent client
+// Copyright (C) 2005-2008, Jari Sundell
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// In addition, as a special exception, the copyright holders give
+// permission to link the code of portions of this program with the
+// OpenSSL library under certain conditions as described in each
+// individual source file, and distribute linked combinations
+// including the two.
+//
+// You must obey the GNU General Public License in all respects for
+// all of the code used other than OpenSSL. If you modify file(s)
+// with this exception, you may extend this exception to your version
+// of the file(s), but you are not obligated to do so. If you do not
+// wish to do so, delete this exception statement from your version.
+// If you delete this exception statement from all source files in the
+// program, then also delete it here.
+//
+// Contact: Jari Sundell
+//
+// Skomakerveien 33
+// 3185 Skoppum, NORWAY
+
+#ifndef RTORRENT_CORE_RANGE_MAP_H
+#define RTORRENT_CORE_RANGE_MAP_H
+
+#include