mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 22:03:31 +01:00
Add an "AccountingRule" feature to permit limiting bw usage by read+write
Patch from "chobe". Closes ticket 961.
This commit is contained in:
parent
e6150c7fc0
commit
8527a29966
@ -1635,19 +1635,31 @@ is non-zero):
|
||||
to 0 will disable the heartbeat. (Default: 6 hours)
|
||||
|
||||
[[AccountingMax]] **AccountingMax** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**|**TBytes**::
|
||||
Never send more than the specified number of bytes in a given accounting
|
||||
period, or receive more than that number in the period. For example, with
|
||||
AccountingMax set to 1 GByte, a server could send 900 MBytes and
|
||||
receive 800 MBytes and continue running. It will only hibernate once
|
||||
one of the two reaches 1 GByte. When the number of bytes gets low,
|
||||
Tor will stop accepting new connections and circuits. When the
|
||||
number of bytes is exhausted, Tor will hibernate until some
|
||||
time in the next accounting period. To prevent all servers from waking at
|
||||
the same time, Tor will also wait until a random point in each period
|
||||
before waking up. If you have bandwidth cost issues, enabling hibernation
|
||||
is preferable to setting a low bandwidth, since it provides users with a
|
||||
collection of fast servers that are up some of the time, which is more
|
||||
useful than a set of slow servers that are always "available".
|
||||
Limits the max number of bytes sent and received within a set time period
|
||||
using a given calculation rule (see: AccountingStart, AccountingRule).
|
||||
Useful if you need to stay under a specific bandwidth. By default, the
|
||||
number used for calculation is the max of either the bytes sent or
|
||||
received. For example, with AccountingMax set to 1 GByte, a server
|
||||
could send 900 MBytes and receive 800 MBytes and continue running.
|
||||
It will only hibernate once one of the two reaches 1 GByte. This can
|
||||
be changed to use the sum of the both bytes received and sent by setting
|
||||
the AccountingRule option to "sum" (total bandwidth in/out). When the
|
||||
number of bytes remaining gets low, Tor will stop accepting new connections
|
||||
and circuits. When the number of bytes is exhausted, Tor will hibernate
|
||||
until some time in the next accounting period. To prevent all servers
|
||||
from waking at the same time, Tor will also wait until a random point
|
||||
in each period before waking up. If you have bandwidth cost issues,
|
||||
enabling hibernation is preferable to setting a low bandwidth, since
|
||||
it provides users with a collection of fast servers that are up some
|
||||
of the time, which is more useful than a set of slow servers that are
|
||||
always "available".
|
||||
|
||||
[[AccountingRule]] **AccountingRule** **sum**|**max**::
|
||||
How we determine when our AccountingMax has been reached (when we
|
||||
should hibernate) during a time interval. Set to "max" to calculate
|
||||
using the higher of either the sent or received bytes (this is the
|
||||
default functionality). Set to "sum" to calculate using the sent
|
||||
plus received bytes.
|
||||
|
||||
[[AccountingStart]] **AccountingStart** **day**|**week**|**month** [__day__] __HH:MM__::
|
||||
Specify how long accounting periods last. If **month** is given, each
|
||||
|
@ -126,6 +126,7 @@ static config_abbrev_t option_abbrevs_[] = {
|
||||
*/
|
||||
static config_var_t option_vars_[] = {
|
||||
V(AccountingMax, MEMUNIT, "0 bytes"),
|
||||
V(AccountingRule, STRING, "max"),
|
||||
V(AccountingStart, STRING, NULL),
|
||||
V(Address, STRING, NULL),
|
||||
V(AllowDotExit, BOOL, "0"),
|
||||
@ -3108,6 +3109,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
|
||||
"risky: they will all turn off at the same time, which may "
|
||||
"alert observers that they are being run by the same party.");
|
||||
}
|
||||
if (options->AccountingRule &&
|
||||
strcmp(options->AccountingRule, "sum") != 0 &&
|
||||
strcmp(options->AccountingRule, "max") != 0)
|
||||
REJECT("AccountingRule must be 'sum' or 'max'");
|
||||
}
|
||||
|
||||
if (options->HTTPProxy) { /* parse it now */
|
||||
|
@ -410,6 +410,16 @@ configure_accounting(time_t now)
|
||||
accounting_set_wakeup_time();
|
||||
}
|
||||
|
||||
/** Return the relevant number of bytes sent/received this interval
|
||||
* based on the set AccountingRule */
|
||||
static uint64_t
|
||||
get_accounting_bytes(void)
|
||||
{
|
||||
if (strcmp(get_options()->AccountingRule, "sum") == 0)
|
||||
return n_bytes_read_in_interval+n_bytes_written_in_interval;
|
||||
return MAX(n_bytes_read_in_interval, n_bytes_written_in_interval);
|
||||
}
|
||||
|
||||
/** Set expected_bandwidth_usage based on how much we sent/received
|
||||
* per minute last interval (if we were up for at least 30 minutes),
|
||||
* or based on our declared bandwidth otherwise. */
|
||||
@ -421,6 +431,11 @@ update_expected_bandwidth(void)
|
||||
uint64_t max_configured = (options->RelayBandwidthRate > 0 ?
|
||||
options->RelayBandwidthRate :
|
||||
options->BandwidthRate) * 60;
|
||||
/* max_configured is the larger of bytes read and bytes written
|
||||
* If we are accounting based on sum, worst case is both are
|
||||
* at max, doubling the expected sum of bandwidth */
|
||||
if (strcmp(get_options()->AccountingRule, "sum") == 0)
|
||||
max_configured *= 2;
|
||||
|
||||
#define MIN_TIME_FOR_MEASUREMENT (1800)
|
||||
|
||||
@ -439,8 +454,7 @@ update_expected_bandwidth(void)
|
||||
* doesn't know to store soft-limit info. Just take rate at which
|
||||
* we were reading/writing in the last interval as our expected rate.
|
||||
*/
|
||||
uint64_t used = MAX(n_bytes_written_in_interval,
|
||||
n_bytes_read_in_interval);
|
||||
uint64_t used = get_accounting_bytes();
|
||||
expected = used / (n_seconds_active_in_interval / 60);
|
||||
} else {
|
||||
/* If we haven't gotten enough data last interval, set 'expected'
|
||||
@ -715,8 +729,7 @@ hibernate_hard_limit_reached(void)
|
||||
uint64_t hard_limit = get_options()->AccountingMax;
|
||||
if (!hard_limit)
|
||||
return 0;
|
||||
return n_bytes_read_in_interval >= hard_limit
|
||||
|| n_bytes_written_in_interval >= hard_limit;
|
||||
return get_accounting_bytes() >= hard_limit;
|
||||
}
|
||||
|
||||
/** Return true iff we have sent/received almost all the bytes we are willing
|
||||
@ -747,8 +760,7 @@ hibernate_soft_limit_reached(void)
|
||||
|
||||
if (!soft_limit)
|
||||
return 0;
|
||||
return n_bytes_read_in_interval >= soft_limit
|
||||
|| n_bytes_written_in_interval >= soft_limit;
|
||||
return get_accounting_bytes() >= soft_limit;
|
||||
}
|
||||
|
||||
/** Called when we get a SIGINT, or when bandwidth soft limit is
|
||||
@ -772,8 +784,7 @@ hibernate_begin(hibernate_state_t new_state, time_t now)
|
||||
hibernate_state == HIBERNATE_STATE_LIVE) {
|
||||
soft_limit_hit_at = now;
|
||||
n_seconds_to_hit_soft_limit = n_seconds_active_in_interval;
|
||||
n_bytes_at_soft_limit = MAX(n_bytes_read_in_interval,
|
||||
n_bytes_written_in_interval);
|
||||
n_bytes_at_soft_limit = get_accounting_bytes();
|
||||
}
|
||||
|
||||
/* close listeners. leave control listener(s). */
|
||||
@ -1003,13 +1014,22 @@ getinfo_helper_accounting(control_connection_t *conn,
|
||||
U64_PRINTF_ARG(n_bytes_written_in_interval));
|
||||
} else if (!strcmp(question, "accounting/bytes-left")) {
|
||||
uint64_t limit = get_options()->AccountingMax;
|
||||
uint64_t read_left = 0, write_left = 0;
|
||||
if (n_bytes_read_in_interval < limit)
|
||||
read_left = limit - n_bytes_read_in_interval;
|
||||
if (n_bytes_written_in_interval < limit)
|
||||
write_left = limit - n_bytes_written_in_interval;
|
||||
tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
|
||||
U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(write_left));
|
||||
if (strcmp(get_options()->AccountingRule, "sum") == 0) {
|
||||
uint64_t total_left = 0;
|
||||
uint64_t total_bytes = get_accounting_bytes();
|
||||
if (total_bytes < limit)
|
||||
total_left = limit - total_bytes;
|
||||
tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
|
||||
U64_PRINTF_ARG(total_left), U64_PRINTF_ARG(total_left));
|
||||
} else {
|
||||
uint64_t read_left = 0, write_left = 0;
|
||||
if (n_bytes_read_in_interval < limit)
|
||||
read_left = limit - n_bytes_read_in_interval;
|
||||
if (n_bytes_written_in_interval < limit)
|
||||
write_left = limit - n_bytes_written_in_interval;
|
||||
tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
|
||||
U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(write_left));
|
||||
}
|
||||
} else if (!strcmp(question, "accounting/interval-start")) {
|
||||
*answer = tor_malloc(ISO_TIME_LEN+1);
|
||||
format_iso_time(*answer, interval_start_time);
|
||||
|
@ -28,6 +28,7 @@ void consider_hibernation(time_t now);
|
||||
int getinfo_helper_accounting(control_connection_t *conn,
|
||||
const char *question, char **answer,
|
||||
const char **errmsg);
|
||||
uint64_t get_accounting_max_total(void);
|
||||
|
||||
#ifdef HIBERNATE_PRIVATE
|
||||
/** Possible values of hibernate_state */
|
||||
|
@ -3775,6 +3775,10 @@ typedef struct {
|
||||
uint64_t AccountingMax; /**< How many bytes do we allow per accounting
|
||||
* interval before hibernation? 0 for "never
|
||||
* hibernate." */
|
||||
char *AccountingRule; /**< How do we determine when our AccountingMax
|
||||
* has been reached?
|
||||
* "max" for when in or out reaches AccountingMax
|
||||
* "sum for when in plus out reaches AccountingMax */
|
||||
|
||||
/** Base64-encoded hash of accepted passwords for the control system. */
|
||||
config_line_t *HashedControlPassword;
|
||||
|
@ -1080,6 +1080,7 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
|
||||
* they're confused or to get statistics. */
|
||||
int interval_length = accounting_get_interval_length();
|
||||
uint32_t effective_bw = get_effective_bwrate(options);
|
||||
uint64_t acc_bytes;
|
||||
if (!interval_length) {
|
||||
log_warn(LD_BUG, "An accounting interval is not allowed to be zero "
|
||||
"seconds long. Raising to 1.");
|
||||
@ -1090,8 +1091,12 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
|
||||
"accounting interval length %d", effective_bw,
|
||||
U64_PRINTF_ARG(options->AccountingMax),
|
||||
interval_length);
|
||||
|
||||
acc_bytes = options->AccountingMax;
|
||||
if (strcmp(options->AccountingRule, "sum") == 0)
|
||||
acc_bytes /= 2;
|
||||
if (effective_bw >=
|
||||
options->AccountingMax / interval_length) {
|
||||
acc_bytes / interval_length) {
|
||||
new_choice = 0;
|
||||
reason = "AccountingMax enabled";
|
||||
}
|
||||
|
@ -145,10 +145,15 @@ log_accounting(const time_t now, const or_options_t *options)
|
||||
or_state_t *state = get_or_state();
|
||||
char *acc_rcvd = bytes_to_usage(state->AccountingBytesReadInInterval);
|
||||
char *acc_sent = bytes_to_usage(state->AccountingBytesWrittenInInterval);
|
||||
char *acc_max = bytes_to_usage(options->AccountingMax);
|
||||
const char *acc_rule = options->AccountingRule;
|
||||
uint64_t acc_bytes = options->AccountingMax;
|
||||
char *acc_max;
|
||||
time_t interval_end = accounting_get_end_time();
|
||||
char end_buf[ISO_TIME_LEN + 1];
|
||||
char *remaining = NULL;
|
||||
if (strcmp(acc_rule, "sum") == 0)
|
||||
acc_bytes *= 2;
|
||||
acc_max = bytes_to_usage(acc_bytes);
|
||||
format_local_iso_time(end_buf, interval_end);
|
||||
remaining = secs_to_uptime(interval_end - now);
|
||||
|
||||
|
@ -34,6 +34,7 @@ src_test_test_SOURCES = \
|
||||
src/test/test_logging.c \
|
||||
src/test/test_microdesc.c \
|
||||
src/test/test_oom.c \
|
||||
src/test/test_accounting.c \
|
||||
src/test/test_options.c \
|
||||
src/test/test_pt.c \
|
||||
src/test/test_relaycell.c \
|
||||
|
@ -1305,6 +1305,7 @@ extern struct testcase_t hs_tests[];
|
||||
extern struct testcase_t nodelist_tests[];
|
||||
extern struct testcase_t routerkeys_tests[];
|
||||
extern struct testcase_t oom_tests[];
|
||||
extern struct testcase_t accounting_tests[];
|
||||
extern struct testcase_t policy_tests[];
|
||||
extern struct testcase_t status_tests[];
|
||||
extern struct testcase_t routerset_tests[];
|
||||
@ -1337,6 +1338,7 @@ static struct testgroup_t testgroups[] = {
|
||||
{ "nodelist/", nodelist_tests },
|
||||
{ "routerkeys/", routerkeys_tests },
|
||||
{ "oom/", oom_tests },
|
||||
{ "accounting/", accounting_tests },
|
||||
{ "policy/" , policy_tests },
|
||||
{ "status/" , status_tests },
|
||||
{ "routerset/" , routerset_tests },
|
||||
|
76
src/test/test_accounting.c
Normal file
76
src/test/test_accounting.c
Normal file
@ -0,0 +1,76 @@
|
||||
#include "or.h"
|
||||
#include "test.h"
|
||||
#define HIBERNATE_PRIVATE
|
||||
#include "hibernate.h"
|
||||
#include "config.h"
|
||||
#define STATEFILE_PRIVATE
|
||||
#include "statefile.h"
|
||||
|
||||
#define NS_MODULE accounting
|
||||
|
||||
#define NS_SUBMODULE limits
|
||||
|
||||
/*
|
||||
* Test to make sure accounting triggers hibernation
|
||||
* correctly with both sum or max rules set
|
||||
*/
|
||||
|
||||
static or_state_t *or_state;
|
||||
NS_DECL(or_state_t *, get_or_state, (void));
|
||||
static or_state_t *
|
||||
NS(get_or_state)(void)
|
||||
{
|
||||
return or_state;
|
||||
}
|
||||
|
||||
static void
|
||||
test_accounting_limits(void *arg)
|
||||
{
|
||||
or_options_t *options = get_options_mutable();
|
||||
time_t fake_time = time(NULL);
|
||||
(void) arg;
|
||||
|
||||
NS_MOCK(get_or_state);
|
||||
or_state = or_state_new();
|
||||
|
||||
options->AccountingMax = 100;
|
||||
options->AccountingRule = tor_strdup("max");
|
||||
|
||||
tor_assert(accounting_is_enabled(options));
|
||||
configure_accounting(fake_time);
|
||||
|
||||
accounting_add_bytes(10, 0, 1);
|
||||
fake_time += 1;
|
||||
consider_hibernation(fake_time);
|
||||
tor_assert(we_are_hibernating() == 0);
|
||||
|
||||
accounting_add_bytes(90, 0, 1);
|
||||
fake_time += 1;
|
||||
consider_hibernation(fake_time);
|
||||
tor_assert(we_are_hibernating() == 1);
|
||||
|
||||
options->AccountingMax = 200;
|
||||
options->AccountingRule = tor_strdup("sum");
|
||||
|
||||
accounting_add_bytes(0, 10, 1);
|
||||
fake_time += 1;
|
||||
consider_hibernation(fake_time);
|
||||
tor_assert(we_are_hibernating() == 0);
|
||||
|
||||
accounting_add_bytes(0, 90, 1);
|
||||
fake_time += 1;
|
||||
consider_hibernation(fake_time);
|
||||
tor_assert(we_are_hibernating() == 1);
|
||||
goto done;
|
||||
done:
|
||||
NS_UNMOCK(get_or_state);
|
||||
or_state_free(or_state);
|
||||
}
|
||||
|
||||
#undef NS_SUBMODULE
|
||||
|
||||
struct testcase_t accounting_tests[] = {
|
||||
{ "bwlimits", test_accounting_limits, TT_FORK, NULL, NULL },
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user