2004-11-07 02:33:06 +01:00
/* Copyright 2004 Roger Dingledine, Nick Mathewson. */
2004-10-31 21:29:25 +01:00
/* See LICENSE for licensing information */
/* $Id$ */
2004-11-29 23:25:31 +01:00
const char hibernate_c_id [ ] = " $Id$ " ;
2004-10-31 21:29:25 +01:00
/**
* \ file hibernate . c
* \ brief Functions to close listeners , stop allowing new circuits ,
2004-11-05 00:39:57 +01:00
* etc in preparation for closing down or going dormant ; and to track
* bandwidth and time intervals to know when to hibernate and when to
* stop hibernating .
2004-10-31 21:29:25 +01:00
* */
/*
hibernating , phase 1 :
- send destroy in response to create cells
- send end ( policy failed ) in response to begin cells
- close an OR conn when it has no circuits
hibernating , phase 2 :
( entered when bandwidth hard limit reached )
- close all OR / AP / exit conns )
*/
# include "or.h"
# define HIBERNATE_STATE_LIVE 1
# define HIBERNATE_STATE_EXITING 2
# define HIBERNATE_STATE_LOWBANDWIDTH 3
# define HIBERNATE_STATE_DORMANT 4
# define SHUTDOWN_WAIT_LENGTH 30 /* seconds */
2004-11-04 23:33:06 +01:00
static int hibernate_state = HIBERNATE_STATE_LIVE ;
2004-11-08 00:14:47 +01:00
/** If are hibernating, when do we plan to wake up? Set to 0 if we
* aren ' t hibernating . */
static time_t hibernate_end_time = 0 ;
2004-11-12 17:39:03 +01:00
typedef enum {
2004-11-22 22:56:51 +01:00
UNIT_MONTH = 1 , UNIT_WEEK = 2 , UNIT_DAY = 3 ,
2004-11-12 17:39:03 +01:00
} time_unit_t ;
2004-11-08 00:14:47 +01:00
/* Fields for accounting logic. Accounting overview:
*
2004-11-09 03:12:41 +01:00
* Accounting is designed to ensure that no more than N bytes are sent
* in either direction over a given interval ( currently , one month ,
* starting at 0 : 00 GMT an arbitrary day within the month ) . We could
2004-11-08 00:14:47 +01:00
* try to do this by choking our bandwidth to a trickle , but that
* would make our streams useless . Instead , we estimate what our
* bandwidth usage will be , and guess how long we ' ll be able to
* provide that much bandwidth before hitting our limit . We then
* choose a random time within the accounting interval to come up ( so
* that we don ' t get 50 Tors running on the 1 st of the month and none
* on the 30 th ) .
*
* Each interval runs as follows :
*
* 1. We guess our bandwidth usage , based on how much we used
* last time . We choose a " wakeup time " within the interval to come up .
* 2. Until the chosen wakeup time , we hibernate .
* 3. We come up at the wakeup time , and provide bandwidth until we are
* " very close " to running out .
* 4. Then we go into low - bandwidth mode , and stop accepting new
* connections , but provide bandwidth until we run out .
* 5. Then we hibernate until the end of the interval .
*
2004-12-01 04:48:14 +01:00
* If the interval ends before we run out of bandwidth , we go back to
2004-11-08 00:14:47 +01:00
* step one .
*/
2004-11-04 23:33:06 +01:00
/** How many bytes have we read/written in this accounting interval? */
static uint64_t n_bytes_read_in_interval = 0 ;
static uint64_t n_bytes_written_in_interval = 0 ;
2004-11-05 18:55:34 +01:00
/** How many seconds have we been running this interval? */
2004-11-04 23:33:06 +01:00
static uint32_t n_seconds_active_in_interval = 0 ;
/** When did this accounting interval start? */
static time_t interval_start_time = 0 ;
/** When will this accounting interval end? */
static time_t interval_end_time = 0 ;
/** How far into the accounting interval should we hibernate? */
static time_t interval_wakeup_time = 0 ;
2004-11-23 23:34:23 +01:00
/** How much bandwidth do we 'expect' to use per minute? (0 if we have no
* info from the last period . ) */
2004-11-05 18:55:34 +01:00
static uint32_t expected_bandwidth_usage = 0 ;
2004-11-22 22:56:51 +01:00
/** What unit are we using for our accounting? */
static time_unit_t cfg_unit = UNIT_MONTH ;
/** How many days,hours,minutes into each unit does our accounting interval
* start ? */
static int cfg_start_day = 0 ;
static int cfg_start_hour = 0 ;
static int cfg_start_min = 0 ;
2004-11-04 23:33:06 +01:00
2004-11-05 18:55:34 +01:00
static void reset_accounting ( time_t now ) ;
2004-11-04 23:33:06 +01:00
static int read_bandwidth_usage ( void ) ;
static time_t start_of_accounting_period_after ( time_t now ) ;
static time_t start_of_accounting_period_containing ( time_t now ) ;
static void accounting_set_wakeup_time ( void ) ;
/* ************
* Functions for bandwidth accounting .
* * * * * * * * * * * * */
2004-11-22 22:56:51 +01:00
/** Configure accounting start/end time settings based on
* options - > AccountingStart . Return 0 on success , - 1 on failure . If
* < b > validate_only < / b > is true , do not change the current settings . */
int
accounting_parse_options ( or_options_t * options , int validate_only )
{
time_unit_t unit ;
int ok , idx ;
long d , h , m ;
smartlist_t * items ;
const char * v = options - > AccountingStart ;
const char * s ;
char * cp ;
if ( ! v ) {
if ( ! validate_only ) {
cfg_unit = UNIT_MONTH ;
cfg_start_day = 1 ;
cfg_start_hour = 0 ;
cfg_start_min = 0 ;
}
return 0 ;
}
items = smartlist_create ( ) ;
smartlist_split_string ( items , v , " " , SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK , 0 ) ;
if ( smartlist_len ( items ) < 2 ) {
log_fn ( LOG_WARN , " Too few arguments to AccountingStart " ) ;
goto err ;
}
s = smartlist_get ( items , 0 ) ;
if ( 0 = = strcasecmp ( s , " month " ) ) {
unit = UNIT_MONTH ;
} else if ( 0 = = strcasecmp ( s , " week " ) ) {
unit = UNIT_WEEK ;
} else if ( 0 = = strcasecmp ( s , " day " ) ) {
unit = UNIT_DAY ;
} else {
log_fn ( LOG_WARN , " Unrecognized accounting unit '%s': only 'month', 'week', and 'day' are supported. " , s ) ;
goto err ;
}
switch ( unit ) {
case UNIT_WEEK :
d = tor_parse_long ( smartlist_get ( items , 1 ) , 10 , 1 , 7 , & ok , NULL ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Weekly accounting must start begin on a day between 1(Monday) and 7 (Sunday) " ) ;
goto err ;
}
break ;
case UNIT_MONTH :
d = tor_parse_long ( smartlist_get ( items , 1 ) , 10 , 1 , 28 , & ok , NULL ) ;
if ( ! ok ) {
2004-12-01 04:48:14 +01:00
log_fn ( LOG_WARN , " Monthly accounting must start begin on a day between 1 and 28 " ) ;
2004-11-22 22:56:51 +01:00
goto err ;
}
break ;
case UNIT_DAY :
d = 0 ;
break ;
default :
tor_assert ( 0 ) ;
}
idx = unit = = UNIT_DAY ? 1 : 2 ;
if ( smartlist_len ( items ) ! = ( idx + 1 ) ) {
log_fn ( LOG_WARN , " Accounting unit '%s' requires %d arguments " ,
s , idx + 1 ) ;
goto err ;
}
s = smartlist_get ( items , idx ) ;
h = tor_parse_long ( s , 10 , 0 , 23 , & ok , & cp ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Accounting start time not parseable: bad hour. " ) ;
goto err ;
}
if ( ! cp | | * cp ! = ' : ' ) {
log_fn ( LOG_WARN , " Accounting start time not parseable: not in HH:MM format " ) ;
goto err ;
}
m = tor_parse_long ( cp + 1 , 10 , 0 , 59 , & ok , & cp ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Accounting start time not parseable: bad minute " ) ;
goto err ;
}
if ( ! cp | | * cp ! = ' \0 ' ) {
log_fn ( LOG_WARN , " Accounting start time not parseable: not in HH:MM format " ) ;
goto err ;
}
if ( ! validate_only ) {
cfg_unit = unit ;
cfg_start_day = ( int ) d ;
cfg_start_hour = ( int ) h ;
cfg_start_min = ( int ) m ;
}
SMARTLIST_FOREACH ( items , char * , s , tor_free ( s ) ) ;
smartlist_free ( items ) ;
return 0 ;
err :
SMARTLIST_FOREACH ( items , char * , s , tor_free ( s ) ) ;
smartlist_free ( items ) ;
return - 1 ;
}
2004-11-15 05:01:31 +01:00
/** If we want to manage the accounting system and potentially
* hibernate , return 1 , else return 0.
*/
int accounting_is_enabled ( or_options_t * options ) {
2004-11-20 01:37:00 +01:00
if ( options - > AccountingMax )
2004-11-15 05:01:31 +01:00
return 1 ;
return 0 ;
}
2004-11-08 00:14:47 +01:00
/** Called from main.c to tell us that <b>seconds</b> seconds have
* passed , < b > n_read < / b > bytes have been read , and < b > n_written < / b >
* bytes have been written . */
2004-11-05 18:55:34 +01:00
void
accounting_add_bytes ( size_t n_read , size_t n_written , int seconds )
2004-11-04 23:33:06 +01:00
{
n_bytes_read_in_interval + = n_read ;
n_bytes_written_in_interval + = n_written ;
/* If we haven't been called in 10 seconds, we're probably jumping
* around in time . */
n_seconds_active_in_interval + = ( seconds < 10 ) ? seconds : 0 ;
}
2004-11-22 22:56:51 +01:00
/** If get_end, return the end of the accounting period that contains
* the time < b > now < / b > . Else , return the start of the accounting
* period that contains the time < b > now < / b > */
static time_t
edge_of_accounting_period_containing ( time_t now , int get_end )
2004-11-05 18:55:34 +01:00
{
2004-11-22 22:56:51 +01:00
int before ;
struct tm * tm ;
tm = localtime ( & now ) ;
/* Set 'before' to true iff the current time is before the hh:mm
* changeover time for today . */
before = tm - > tm_hour < cfg_start_hour | |
( tm - > tm_hour = = cfg_start_hour & & tm - > tm_min < cfg_start_min ) ;
/* Dispatch by unit. First, find the start day of the given period;
* then , if get_end is true , increment to the end day . */
switch ( cfg_unit )
{
case UNIT_MONTH : {
/* If this is before the Nth, we want the Nth of last month. */
if ( tm - > tm_mday < cfg_start_day | |
( tm - > tm_mday < cfg_start_day & & before ) ) {
- - tm - > tm_mon ;
}
/* Otherwise, the month is correct. */
tm - > tm_mday = cfg_start_day ;
if ( get_end )
+ + tm - > tm_mon ;
break ;
}
case UNIT_WEEK : {
/* What is the 'target' day of the week in struct tm format? (We
say Sunday = = 7 ; struct tm says Sunday = = 0. ) */
int wday = cfg_start_day % 7 ;
/* How many days do we subtract from today to get to the right day? */
int delta = ( 7 + tm - > tm_wday - wday ) % 7 ;
/* If we are on the right day, but the changeover hasn't happened yet,
* then subtract a whole week . */
if ( delta = = 0 & & before )
delta = 7 ;
tm - > tm_mday - = delta ;
if ( get_end )
tm - > tm_mday + = 7 ;
break ;
}
case UNIT_DAY :
if ( before )
- - tm - > tm_mday ;
if ( get_end )
+ + tm - > tm_mday ;
break ;
default :
tor_assert ( 0 ) ;
2004-11-05 18:55:34 +01:00
}
2004-11-22 22:56:51 +01:00
tm - > tm_hour = cfg_start_hour ;
tm - > tm_min = cfg_start_min ;
tm - > tm_sec = 0 ;
tm - > tm_isdst = - 1 ; /* Autodetect DST */
return mktime ( tm ) ;
2004-11-05 18:55:34 +01:00
}
2004-11-22 22:56:51 +01:00
/** Return the start of the accounting period containing the time
* < b > now < / b > . */
2004-11-05 18:55:34 +01:00
static time_t
start_of_accounting_period_containing ( time_t now )
2004-11-04 23:33:06 +01:00
{
2004-11-22 22:56:51 +01:00
return edge_of_accounting_period_containing ( now , 0 ) ;
2004-11-04 23:33:06 +01:00
}
2004-11-08 00:14:47 +01:00
/** Return the start of the accounting period that comes after the one
* containing the time < b > now < / b > . */
2004-11-05 18:55:34 +01:00
static time_t
start_of_accounting_period_after ( time_t now )
2004-11-04 23:33:06 +01:00
{
2004-11-22 22:56:51 +01:00
return edge_of_accounting_period_containing ( now , 1 ) ;
2004-11-04 23:33:06 +01:00
}
2004-11-08 00:14:47 +01:00
/** Initialize the accounting subsystem. */
2004-11-05 18:55:34 +01:00
void
configure_accounting ( time_t now )
2004-11-04 23:33:06 +01:00
{
2004-11-08 00:14:47 +01:00
/* Try to remember our recorded usage. */
2004-11-04 23:33:06 +01:00
if ( ! interval_start_time )
2004-11-05 18:55:34 +01:00
read_bandwidth_usage ( ) ; /* If we fail, we'll leave values at zero, and
* reset below . */
2004-11-04 23:33:06 +01:00
if ( ! interval_start_time | |
start_of_accounting_period_after ( interval_start_time ) < = now ) {
2004-11-08 00:14:47 +01:00
/* We didn't have recorded usage, or we don't have recorded usage
* for this interval . Start a new interval . */
2004-11-04 23:33:06 +01:00
log_fn ( LOG_INFO , " Starting new accounting interval. " ) ;
2004-11-05 18:55:34 +01:00
reset_accounting ( now ) ;
2004-11-22 22:56:51 +01:00
} else if ( interval_start_time = =
2004-11-04 23:33:06 +01:00
start_of_accounting_period_containing ( interval_start_time ) ) {
log_fn ( LOG_INFO , " Continuing accounting interval. " ) ;
/* We are in the interval we thought we were in. Do nothing.*/
2004-11-10 05:19:53 +01:00
interval_end_time = start_of_accounting_period_after ( interval_start_time ) ;
2004-11-04 23:33:06 +01:00
} else {
2004-11-05 18:55:34 +01:00
log_fn ( LOG_WARN , " Mismatched accounting interval; starting a fresh one. " ) ;
reset_accounting ( now ) ;
2004-11-04 23:33:06 +01:00
}
accounting_set_wakeup_time ( ) ;
}
2004-11-08 00:14:47 +01:00
/** 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 . */
2004-11-05 18:55:34 +01:00
static void
update_expected_bandwidth ( void )
{
2004-11-22 23:24:10 +01:00
uint64_t used , expected ;
uint64_t max_configured = ( get_options ( ) - > BandwidthRate * 60 ) ;
2004-11-05 18:55:34 +01:00
if ( n_seconds_active_in_interval < 1800 ) {
2004-11-23 23:34:23 +01:00
/* If we haven't gotten enough data last interval, set 'expected'
* to 0. This will set our wakeup to the start of the interval .
* Next interval , we ' ll choose our starting time based on how much
* we sent this interval .
2004-11-16 04:32:01 +01:00
*/
2004-11-23 23:34:23 +01:00
expected = 0 ;
2004-11-05 18:55:34 +01:00
} else {
used = n_bytes_written_in_interval < n_bytes_read_in_interval ?
n_bytes_read_in_interval : n_bytes_written_in_interval ;
2004-11-23 23:34:23 +01:00
expected = used / ( n_seconds_active_in_interval / 60 ) ;
2004-11-22 23:24:10 +01:00
if ( expected > max_configured )
expected = max_configured ;
2004-11-05 18:55:34 +01:00
}
2004-11-22 23:24:10 +01:00
if ( expected > UINT32_MAX )
expected = UINT32_MAX ;
expected_bandwidth_usage = ( uint32_t ) expected ;
2004-11-05 18:55:34 +01:00
}
2004-11-08 00:14:47 +01:00
/** Called at the start of a new accounting interval: reset our
* expected bandwidth usage based on what happened last time , set up
* the start and end of the interval , and clear byte / time totals .
*/
2004-11-05 18:55:34 +01:00
static void
reset_accounting ( time_t now ) {
log_fn ( LOG_INFO , " Starting new accounting interval. " ) ;
update_expected_bandwidth ( ) ;
interval_start_time = start_of_accounting_period_containing ( now ) ;
interval_end_time = start_of_accounting_period_after ( interval_start_time ) ;
n_bytes_read_in_interval = 0 ;
n_bytes_written_in_interval = 0 ;
n_seconds_active_in_interval = 0 ;
}
2004-11-08 00:14:47 +01:00
/** Return true iff we should save our bandwidth usage to disk. */
static INLINE int
time_to_record_bandwidth_usage ( time_t now )
2004-11-04 23:33:06 +01:00
{
2004-11-10 05:19:53 +01:00
/* Note every 60 sec */
# define NOTE_INTERVAL (60)
2004-11-04 23:33:06 +01:00
/* Or every 20 megabytes */
# define NOTE_BYTES 20*(1024*1024)
static uint64_t last_read_bytes_noted = 0 ;
static uint64_t last_written_bytes_noted = 0 ;
static time_t last_time_noted = 0 ;
2004-11-05 18:55:34 +01:00
if ( last_time_noted + NOTE_INTERVAL < = now | |
last_read_bytes_noted + NOTE_BYTES < = n_bytes_read_in_interval | |
last_written_bytes_noted + NOTE_BYTES < = n_bytes_written_in_interval | |
( interval_end_time & & interval_end_time < = now ) ) {
2004-11-04 23:33:06 +01:00
last_time_noted = now ;
last_read_bytes_noted = n_bytes_read_in_interval ;
last_written_bytes_noted = n_bytes_written_in_interval ;
return 1 ;
}
return 0 ;
}
2004-11-05 18:55:34 +01:00
void
accounting_run_housekeeping ( time_t now )
2004-11-04 23:33:06 +01:00
{
if ( now > = interval_end_time ) {
configure_accounting ( now ) ;
}
if ( time_to_record_bandwidth_usage ( now ) ) {
2004-11-14 23:53:51 +01:00
if ( accounting_record_bandwidth_usage ( now ) ) {
2004-11-05 18:55:34 +01:00
log_fn ( LOG_ERR , " Couldn't record bandwidth usage; exiting. " ) ;
exit ( 1 ) ;
2004-11-04 23:33:06 +01:00
}
}
}
2004-11-08 00:14:47 +01:00
/** Based on our interval and our estimated bandwidth, choose a
* deterministic ( but random - ish ) time to wake up . */
2004-11-05 18:55:34 +01:00
static void
accounting_set_wakeup_time ( void )
2004-11-04 23:33:06 +01:00
{
char buf [ ISO_TIME_LEN + 1 ] ;
char digest [ DIGEST_LEN ] ;
2004-11-22 22:56:51 +01:00
crypto_digest_env_t * d_env ;
int time_in_interval ;
int time_to_exhaust_bw ;
int time_to_consider ;
2004-11-04 23:33:06 +01:00
2004-11-21 05:19:04 +01:00
if ( ! identity_key_is_set ( ) ) {
if ( init_keys ( ) < 0 ) {
log_fn ( LOG_ERR , " Error initializing keys " ) ;
tor_assert ( 0 ) ;
}
}
2004-11-04 23:33:06 +01:00
format_iso_time ( buf , interval_start_time ) ;
crypto_pk_get_digest ( get_identity_key ( ) , digest ) ;
2004-11-22 22:56:51 +01:00
d_env = crypto_new_digest_env ( ) ;
crypto_digest_add_bytes ( d_env , buf , ISO_TIME_LEN ) ;
crypto_digest_add_bytes ( d_env , digest , DIGEST_LEN ) ;
crypto_digest_get_digest ( d_env , digest , DIGEST_LEN ) ;
crypto_free_digest_env ( d_env ) ;
2004-11-04 23:33:06 +01:00
2004-11-23 23:34:23 +01:00
if ( ! expected_bandwidth_usage ) {
char buf1 [ ISO_TIME_LEN + 1 ] ;
char buf2 [ ISO_TIME_LEN + 1 ] ;
format_local_iso_time ( buf1 , interval_start_time ) ;
format_local_iso_time ( buf2 , interval_end_time ) ;
2004-11-22 22:56:51 +01:00
time_to_exhaust_bw = 24 * 60 * 60 ;
2004-11-23 23:34:23 +01:00
interval_wakeup_time = interval_start_time ;
2004-11-04 23:33:06 +01:00
2004-11-23 23:34:23 +01:00
log_fn ( LOG_NOTICE , " Configured hibernation. This interval begins at %s "
" and ends at %s. We have no prior estimate for bandwidth, so "
2004-12-05 13:26:02 +01:00
" we will start out awake and hibernate when we exhaust our quota. " ,
2004-11-23 23:34:23 +01:00
buf1 , buf2 ) ;
return ;
}
time_to_exhaust_bw = ( int )
( get_options ( ) - > AccountingMax / expected_bandwidth_usage ) * 60 ;
2004-11-22 22:56:51 +01:00
time_in_interval = interval_end_time - interval_start_time ;
time_to_consider = time_in_interval - time_to_exhaust_bw ;
2004-11-23 23:34:23 +01:00
2004-11-22 22:56:51 +01:00
if ( time_to_consider < = 0 ) {
interval_wakeup_time = interval_start_time ;
} else {
/* XXX can we simplify this just by picking a random (non-deterministic)
* time to be up ? If we go down and come up , then we pick a new one . Is
* that good enough ? - RD */
2004-11-04 23:33:06 +01:00
2004-11-22 22:56:51 +01:00
/* This is not a perfectly unbiased conversion, but it is good enough:
* in the worst case , the first half of the day is 0.06 percent likelier
* to be chosen than the last half . */
interval_wakeup_time = interval_start_time +
( get_uint32 ( digest ) % time_to_consider ) ;
2004-11-14 22:11:06 +01:00
2004-11-22 22:56:51 +01:00
format_iso_time ( buf , interval_wakeup_time ) ;
}
2004-11-23 23:34:23 +01:00
2004-11-22 22:56:51 +01:00
{
char buf1 [ ISO_TIME_LEN + 1 ] ;
char buf2 [ ISO_TIME_LEN + 1 ] ;
char buf3 [ ISO_TIME_LEN + 1 ] ;
char buf4 [ ISO_TIME_LEN + 1 ] ;
time_t down_time = interval_wakeup_time + time_to_exhaust_bw ;
if ( down_time > interval_end_time )
down_time = interval_end_time ;
format_local_iso_time ( buf1 , interval_start_time ) ;
format_local_iso_time ( buf2 , interval_wakeup_time ) ;
2004-11-23 23:34:23 +01:00
format_local_iso_time ( buf3 ,
down_time < interval_end_time ? down_time : interval_end_time ) ;
2004-11-22 22:56:51 +01:00
format_local_iso_time ( buf4 , interval_end_time ) ;
2004-11-23 23:34:23 +01:00
log_fn ( LOG_NOTICE , " Configured hibernation. This interval began at %s; "
" the scheduled wake-up time %s %s; "
2004-12-05 13:26:02 +01:00
" we expect%s to exhaust our quota for this interval around %s; "
2004-11-23 23:34:23 +01:00
" the next interval begins at %s (all times local) " ,
buf1 ,
time ( NULL ) < interval_wakeup_time ? " is " : " was " , buf2 ,
time ( NULL ) < down_time ? " " : " ed " , buf3 ,
buf4 ) ;
2004-11-22 22:56:51 +01:00
}
2004-10-31 21:29:25 +01:00
}
2004-11-05 18:55:34 +01:00
# define BW_ACCOUNTING_VERSION 1
2004-11-08 00:14:47 +01:00
/** Save all our bandwidth tracking information to disk. Return 0 on
* success , - 1 on failure */
2004-11-14 23:21:23 +01:00
int
accounting_record_bandwidth_usage ( time_t now )
2004-11-04 23:33:06 +01:00
{
char buf [ 128 ] ;
char fname [ 512 ] ;
2004-11-05 18:55:34 +01:00
char time1 [ ISO_TIME_LEN + 1 ] ;
char time2 [ ISO_TIME_LEN + 1 ] ;
2004-11-04 23:33:06 +01:00
char * cp = buf ;
2004-11-05 18:55:34 +01:00
/* Format is:
Version \ nTime \ nTime \ nRead \ nWrite \ nSeconds \ nExpected - Rate \ n */
format_iso_time ( time1 , interval_start_time ) ;
format_iso_time ( time2 , now ) ;
tor_snprintf ( cp , sizeof ( buf ) ,
" %d \n %s \n %s \n " U64_FORMAT " \n " U64_FORMAT " \n %lu \n %lu \n " ,
BW_ACCOUNTING_VERSION ,
time1 ,
time2 ,
2004-11-04 23:33:06 +01:00
U64_PRINTF_ARG ( n_bytes_read_in_interval ) ,
U64_PRINTF_ARG ( n_bytes_written_in_interval ) ,
2004-11-05 18:55:34 +01:00
( unsigned long ) n_seconds_active_in_interval ,
( unsigned long ) expected_bandwidth_usage ) ;
2004-11-04 23:33:06 +01:00
tor_snprintf ( fname , sizeof ( fname ) , " %s/bw_accounting " ,
2004-11-09 08:05:53 +01:00
get_options ( ) - > DataDirectory ) ;
2004-11-04 23:33:06 +01:00
return write_str_to_file ( fname , buf , 0 ) ;
2004-10-31 21:29:25 +01:00
}
2004-11-08 00:14:47 +01:00
/** Read stored accounting information from disk. Return 0 on success;
* return - 1 and change nothing on failure . */
static int
read_bandwidth_usage ( void )
2004-11-04 23:33:06 +01:00
{
char * s = NULL ;
char fname [ 512 ] ;
2004-11-05 18:55:34 +01:00
time_t t1 , t2 ;
uint64_t n_read , n_written ;
uint32_t expected_bw , n_seconds ;
smartlist_t * elts ;
int ok ;
2004-11-04 23:33:06 +01:00
tor_snprintf ( fname , sizeof ( fname ) , " %s/bw_accounting " ,
2004-11-09 08:05:53 +01:00
get_options ( ) - > DataDirectory ) ;
2004-11-04 23:33:06 +01:00
if ( ! ( s = read_file_to_str ( fname , 0 ) ) ) {
return 0 ;
}
2004-11-05 18:55:34 +01:00
elts = smartlist_create ( ) ;
2004-11-10 05:19:53 +01:00
smartlist_split_string ( elts , s , " \n " , SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK , 0 ) ;
2004-11-05 18:55:34 +01:00
tor_free ( s ) ;
if ( smartlist_len ( elts ) < 1 | |
atoi ( smartlist_get ( elts , 0 ) ) ! = BW_ACCOUNTING_VERSION ) {
log_fn ( LOG_WARN , " Unrecognized bw_accounting file version: %s " ,
( const char * ) smartlist_get ( elts , 0 ) ) ;
2004-11-04 23:33:06 +01:00
goto err ;
}
2004-11-05 18:55:34 +01:00
if ( smartlist_len ( elts ) < 7 ) {
log_fn ( LOG_WARN , " Corrupted bw_accounting file: %d lines " ,
smartlist_len ( elts ) ) ;
2004-11-04 23:33:06 +01:00
goto err ;
}
2004-11-05 18:55:34 +01:00
if ( parse_iso_time ( smartlist_get ( elts , 1 ) , & t1 ) ) {
2004-11-04 23:33:06 +01:00
log_fn ( LOG_WARN , " Error parsing bandwidth usage start time. " ) ;
goto err ;
}
2004-11-05 18:55:34 +01:00
if ( parse_iso_time ( smartlist_get ( elts , 2 ) , & t2 ) ) {
log_fn ( LOG_WARN , " Error parsing bandwidth usage last-written time " ) ;
2004-11-04 23:33:06 +01:00
goto err ;
}
2004-11-05 18:55:34 +01:00
n_read = tor_parse_uint64 ( smartlist_get ( elts , 3 ) , 10 , 0 , UINT64_MAX ,
& ok , NULL ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Error parsing number of bytes read " ) ;
goto err ;
}
n_written = tor_parse_uint64 ( smartlist_get ( elts , 4 ) , 10 , 0 , UINT64_MAX ,
& ok , NULL ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Error parsing number of bytes read " ) ;
2004-11-04 23:33:06 +01:00
goto err ;
}
2004-11-05 18:55:34 +01:00
n_seconds = ( uint32_t ) tor_parse_ulong ( smartlist_get ( elts , 5 ) , 10 , 0 , ULONG_MAX ,
& ok , NULL ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Error parsing number of seconds live " ) ;
2004-11-04 23:33:06 +01:00
goto err ;
}
2004-11-05 18:55:34 +01:00
expected_bw = ( uint32_t ) tor_parse_ulong ( smartlist_get ( elts , 6 ) , 10 , 0 , ULONG_MAX ,
& ok , NULL ) ;
if ( ! ok ) {
log_fn ( LOG_WARN , " Error parsing expected bandwidth " ) ;
2004-11-04 23:33:06 +01:00
goto err ;
}
2004-11-05 18:55:34 +01:00
n_bytes_read_in_interval = n_read ;
n_bytes_written_in_interval = n_written ;
n_seconds_active_in_interval = n_seconds ;
interval_start_time = t1 ;
expected_bandwidth_usage = expected_bw ;
2004-12-08 00:20:10 +01:00
log_fn ( LOG_INFO , " Successfully read bandwidth accounting file written at %s for interval starting at %s. We have been active for %lu seconds in this interval. At the start of the interval, we expected to use about %lu KB per second. ( " U64_FORMAT " bytes read so far, " U64_FORMAT " bytes written so far) " ,
( char * ) smartlist_get ( elts , 2 ) ,
( char * ) smartlist_get ( elts , 1 ) ,
( unsigned long ) n_seconds_active_in_interval ,
( unsigned long ) ( ( uint64_t ) expected_bandwidth_usage * 1024 / 60 ) ,
2004-12-08 13:30:20 +01:00
U64_PRINTF_ARG ( n_bytes_read_in_interval ) ,
U64_PRINTF_ARG ( n_bytes_written_in_interval ) ) ;
2004-12-08 00:20:10 +01:00
SMARTLIST_FOREACH ( elts , char * , cp , tor_free ( cp ) ) ;
smartlist_free ( elts ) ;
2004-11-04 23:33:06 +01:00
return 0 ;
err :
2004-11-05 18:55:34 +01:00
SMARTLIST_FOREACH ( elts , char * , cp , tor_free ( cp ) ) ;
smartlist_free ( elts ) ;
2004-11-04 23:33:06 +01:00
return - 1 ;
}
2004-11-08 00:14:47 +01:00
/** Return true iff we have sent/received all the bytes we are willing
* to send / receive this interval . */
static int
hibernate_hard_limit_reached ( void )
2004-11-04 23:33:06 +01:00
{
2004-11-20 01:37:00 +01:00
uint64_t hard_limit = get_options ( ) - > AccountingMax ;
2004-11-04 23:33:06 +01:00
if ( ! hard_limit )
return 0 ;
return n_bytes_read_in_interval > = hard_limit
| | n_bytes_written_in_interval > = hard_limit ;
}
2004-11-08 00:14:47 +01:00
/** Return true iff we have sent/received almost all the bytes we are willing
* to send / receive this interval . */
2004-11-04 23:33:06 +01:00
static int hibernate_soft_limit_reached ( void )
{
2004-11-20 01:37:00 +01:00
uint64_t soft_limit = ( uint64_t ) ( ( get_options ( ) - > AccountingMax ) * .99 ) ;
2004-11-04 23:33:06 +01:00
if ( ! soft_limit )
return 0 ;
return n_bytes_read_in_interval > = soft_limit
| | n_bytes_written_in_interval > = soft_limit ;
}
2004-11-08 00:14:47 +01:00
/** Called when we get a SIGINT, or when bandwidth soft limit is
* reached . Puts us into " loose hibernation " : we don ' t accept new
* connections , but we continue handling old ones . */
2004-11-05 18:55:34 +01:00
static void hibernate_begin ( int new_state , time_t now ) {
2004-10-31 21:29:25 +01:00
connection_t * conn ;
2004-11-28 10:05:49 +01:00
if ( hibernate_state = = HIBERNATE_STATE_EXITING ) {
2004-10-31 21:29:25 +01:00
/* we've been called twice now. close immediately. */
log ( LOG_NOTICE , " Second sigint received; exiting now. " ) ;
tor_cleanup ( ) ;
exit ( 0 ) ;
}
2004-11-09 03:12:41 +01:00
/* close listeners. leave control listener(s). */
2004-11-28 10:05:49 +01:00
while ( ( conn = connection_get_by_type ( CONN_TYPE_OR_LISTENER ) ) | |
( conn = connection_get_by_type ( CONN_TYPE_AP_LISTENER ) ) | |
( conn = connection_get_by_type ( CONN_TYPE_DIR_LISTENER ) ) ) {
2004-10-31 21:29:25 +01:00
log_fn ( LOG_INFO , " Closing listener type %d " , conn - > type ) ;
connection_mark_for_close ( conn ) ;
}
/* XXX kill intro point circs */
/* XXX upload rendezvous service descriptors with no intro points */
2004-11-28 10:05:49 +01:00
if ( new_state = = HIBERNATE_STATE_EXITING ) {
2004-10-31 21:29:25 +01:00
log ( LOG_NOTICE , " Interrupt: will shut down in %d seconds. Interrupt again to exit now. " , SHUTDOWN_WAIT_LENGTH ) ;
2004-11-08 00:14:47 +01:00
hibernate_end_time = time ( NULL ) + SHUTDOWN_WAIT_LENGTH ;
2004-10-31 21:29:25 +01:00
} else { /* soft limit reached */
2004-11-08 00:14:47 +01:00
hibernate_end_time = interval_end_time ;
2004-10-31 21:29:25 +01:00
}
hibernate_state = new_state ;
2004-11-14 23:21:23 +01:00
accounting_record_bandwidth_usage ( now ) ;
2004-10-31 21:29:25 +01:00
}
/** Called when we've been hibernating and our timeout is reached. */
2004-11-08 00:14:47 +01:00
static void
hibernate_end ( int new_state ) {
2004-10-31 21:29:25 +01:00
tor_assert ( hibernate_state = = HIBERNATE_STATE_LOWBANDWIDTH | |
hibernate_state = = HIBERNATE_STATE_DORMANT ) ;
/* listeners will be relaunched in run_scheduled_events() in main.c */
log_fn ( LOG_NOTICE , " Hibernation period ended. Resuming normal activity. " ) ;
hibernate_state = new_state ;
2004-11-08 00:14:47 +01:00
hibernate_end_time = 0 ; /* no longer hibernating */
2004-10-31 21:29:25 +01:00
}
/** A wrapper around hibernate_begin, for when we get SIGINT. */
2004-11-08 00:14:47 +01:00
void
hibernate_begin_shutdown ( void ) {
2004-11-05 18:55:34 +01:00
hibernate_begin ( HIBERNATE_STATE_EXITING , time ( NULL ) ) ;
2004-10-31 21:29:25 +01:00
}
2004-11-08 00:14:47 +01:00
/** Return true iff we are currently hibernating. */
int
we_are_hibernating ( void ) {
2004-10-31 21:29:25 +01:00
return hibernate_state ! = HIBERNATE_STATE_LIVE ;
}
2004-11-08 00:14:47 +01:00
/** If we aren't currently dormant, close all connections and become
* dormant . */
static void
2004-11-14 22:11:06 +01:00
hibernate_go_dormant ( time_t now ) {
2004-11-08 00:14:47 +01:00
connection_t * conn ;
if ( hibernate_state = = HIBERNATE_STATE_DORMANT )
return ;
2004-11-14 22:11:06 +01:00
else if ( hibernate_state = = HIBERNATE_STATE_LOWBANDWIDTH )
hibernate_state = HIBERNATE_STATE_DORMANT ;
else
hibernate_begin ( HIBERNATE_STATE_DORMANT , now ) ;
2004-11-08 00:14:47 +01:00
log_fn ( LOG_NOTICE , " Going dormant. Blowing away remaining connections. " ) ;
2004-11-09 03:12:41 +01:00
/* Close all OR/AP/exit conns. Leave dir conns because we still want
* to be able to upload server descriptors so people know we ' re still
* running , and download directories so we can detect if we ' re obsolete .
* Leave control conns because we still want to be controllable .
*/
2004-11-28 10:05:49 +01:00
while ( ( conn = connection_get_by_type ( CONN_TYPE_OR ) ) | |
( conn = connection_get_by_type ( CONN_TYPE_AP ) ) | |
( conn = connection_get_by_type ( CONN_TYPE_EXIT ) ) ) {
2004-12-05 14:02:18 +01:00
if ( CONN_IS_EDGE ( conn ) )
connection_edge_end ( conn , END_STREAM_REASON_MISC , conn - > cpath_layer ) ;
2004-11-08 00:14:47 +01:00
log_fn ( LOG_INFO , " Closing conn type %d " , conn - > type ) ;
connection_mark_for_close ( conn ) ;
}
2004-11-10 05:19:53 +01:00
2004-11-14 23:21:23 +01:00
accounting_record_bandwidth_usage ( now ) ;
2004-11-08 00:14:47 +01:00
}
/** Called when hibernate_end_time has arrived. */
static void
hibernate_end_time_elapsed ( time_t now )
{
2004-11-14 22:11:06 +01:00
char buf [ ISO_TIME_LEN + 1 ] ;
2004-11-08 00:14:47 +01:00
/* The interval has ended, or it is wakeup time. Find out which. */
accounting_run_housekeeping ( now ) ;
if ( interval_wakeup_time < = now ) {
/* The interval hasn't changed, but interval_wakeup_time has passed.
* It ' s time to wake up and start being a server . */
hibernate_end ( HIBERNATE_STATE_LIVE ) ;
return ;
} else {
/* The interval has changed, and it isn't time to wake up yet. */
hibernate_end_time = interval_wakeup_time ;
2004-11-14 22:11:06 +01:00
format_iso_time ( buf , interval_wakeup_time ) ;
if ( hibernate_state ! = HIBERNATE_STATE_DORMANT ) {
2004-11-08 00:14:47 +01:00
/* We weren't sleeping before; we should sleep now. */
2004-11-14 22:11:06 +01:00
log_fn ( LOG_NOTICE , " Accounting period ended. Commencing hibernation until %s GMT " , buf ) ;
hibernate_go_dormant ( now ) ;
} else {
log_fn ( LOG_NOTICE , " Accounting period ended. This period, we will hibernate until %s GMT " , buf ) ;
}
2004-11-08 00:14:47 +01:00
}
}
2004-12-13 01:44:39 +01:00
/** Consider our environment and decide if it's time
2004-11-08 00:14:47 +01:00
* to start / stop hibernating .
2004-10-31 21:29:25 +01:00
*/
void consider_hibernation ( time_t now ) {
2004-11-20 01:37:00 +01:00
int accounting_enabled = get_options ( ) - > AccountingMax ! = 0 ;
2004-11-14 22:11:06 +01:00
char buf [ ISO_TIME_LEN + 1 ] ;
2004-10-31 21:29:25 +01:00
2004-11-09 03:12:41 +01:00
/* If we're in 'exiting' mode, then we just shut down after the interval
2004-11-08 00:14:47 +01:00
* elapses . */
2004-10-31 21:29:25 +01:00
if ( hibernate_state = = HIBERNATE_STATE_EXITING ) {
2004-11-08 00:14:47 +01:00
tor_assert ( hibernate_end_time ) ;
2004-11-28 10:05:49 +01:00
if ( hibernate_end_time < = now ) {
2004-10-31 21:29:25 +01:00
log ( LOG_NOTICE , " Clean shutdown finished. Exiting. " ) ;
tor_cleanup ( ) ;
exit ( 0 ) ;
}
return ; /* if exiting soon, don't worry about bandwidth limits */
}
2004-11-28 10:05:49 +01:00
if ( hibernate_state = = HIBERNATE_STATE_DORMANT ) {
2004-11-05 18:55:34 +01:00
/* We've been hibernating because of bandwidth accounting. */
2004-11-08 00:14:47 +01:00
tor_assert ( hibernate_end_time ) ;
2004-11-14 22:11:06 +01:00
if ( hibernate_end_time > now & & accounting_enabled ) {
2004-11-05 18:55:34 +01:00
/* If we're hibernating, don't wake up until it's time, regardless of
2004-11-08 00:14:47 +01:00
* whether we ' re in a new interval . */
2004-11-05 18:55:34 +01:00
return ;
} else {
2004-11-08 00:14:47 +01:00
hibernate_end_time_elapsed ( now ) ;
2004-11-05 18:55:34 +01:00
}
2004-10-31 21:29:25 +01:00
}
2004-11-08 00:14:47 +01:00
/* Else, we aren't hibernating. See if it's time to start hibernating, or to
* go dormant . */
2004-11-14 22:11:06 +01:00
if ( hibernate_state = = HIBERNATE_STATE_LIVE ) {
if ( hibernate_soft_limit_reached ( ) ) {
log_fn ( LOG_NOTICE , " Bandwidth soft limit reached; commencing hibernation. " ) ;
hibernate_begin ( HIBERNATE_STATE_LOWBANDWIDTH , now ) ;
} else if ( accounting_enabled & & now < interval_wakeup_time ) {
format_iso_time ( buf , interval_wakeup_time ) ;
log_fn ( LOG_NOTICE , " Commencing hibernation. We will wake up at %s GMT " , buf ) ;
hibernate_go_dormant ( now ) ;
}
2004-10-31 21:29:25 +01:00
}
2004-11-08 00:14:47 +01:00
if ( hibernate_state = = HIBERNATE_STATE_LOWBANDWIDTH ) {
2004-11-14 22:11:06 +01:00
if ( ! accounting_enabled ) {
hibernate_end_time_elapsed ( now ) ;
} else if ( hibernate_hard_limit_reached ( ) ) {
hibernate_go_dormant ( now ) ;
2004-11-08 00:14:47 +01:00
} else if ( hibernate_end_time < = now ) {
/* The hibernation period ended while we were still in lowbandwidth.*/
hibernate_end_time_elapsed ( now ) ;
2004-10-31 21:29:25 +01:00
}
}
}
2004-11-23 08:37:25 +01:00