Tor源码文件分析 — Hibernation

  本篇会介绍Tor系统的休眠模块。休眠模块的代码处于源文件Hibernation.c之中。简单的说,其主要作用就是在适当的时机将系统进入休眠状态以保护系统资源被过度消耗;或者在适当的时机重新唤醒系统以达到重新为全局服务的目的。在默认的系统配置下,客户端的休眠模块是被关闭的,也就是说客户端永远不会进入休眠态。而Tor系统中的工作路由服务器则并非如此。他们很多时候需要设置一些为网络服务的策略和带宽,那么他们就需要对自己为Tor系统做出的贡献做一定的限制。单纯从流量角度来说,或许有一些OR服务器不愿意在一段时间内,允许流过自身的数据量超过一个非常大的范围。所以,OR服务器运行其自身的Tor程序时,进行了相关配置,要求其检查固定时间内流过的数据量。如果该数据量较小,则服务器可以忍受;若数据量非常大,则服务器会让其Tor程序进入休眠状态,提供少量服务,甚至最后不提供服务。

  在讲休眠机制之前,需要关注配置文件中的两个配置选项:AccountingMax,AccountingStart。服务器就是通过这两个配置项,来配置他们所能接受的流量范围。我们以默认情况举例。一台Tor路由服务器,一般情况下统计其流量的周期为一个月,一个月内允许通过其自身的Tor网络的流量为1G,则上述两个配置项可以根据周期为月,流量为1G这些需求进行配置。具体在配置文件中如何具体配置,请参照Tor Manual中的选项说明。

  这里我们先来分析休眠模块的源文件的全局变量及部分函数。

0. 概述

  概述部分我们只简单地贴出来源文件头部对文件的说明,因为其已经相对清晰地描述了文件所要处理的事件和主要工作内容。

/**
 * \file hibernate.c
 * \brief Functions to close listeners, stop allowing new circuits,
 * 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.
 **/

/*
hibernating, phase 1: // soft limit 将要耗尽流量之时产生的反应
  - 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: // hard limit 流量耗尽之时产生的反应
  (entered when bandwidth hard limit reached)
  - close all OR/AP/exit conns)
*/

  简而言之,系统是否休眠取决于在指定的时间段内,系统的流量多少。源文件中的注释中给出了如下的关于休眠和统计流量的简要解释:

/* Fields for accounting logic.  Accounting overview:
 *
 * Accounting is designed to ensure that no more than N bytes are sent in
 * either direction over a given interval (currently, one month, one week, or
 * one day) We could
 * 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 1st of the month and none
 * on the 30th).
 *
 * 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.                       //流量耗尽之后,系统进入休眠态,直到下一个周期决定系统唤醒时间点。
 *
 * If the interval ends before we run out of bandwidth, we go back to
 * step one.
 */

1. 全局变量

/** Are we currently awake, asleep, running out of bandwidth, or shutting
 * down? */
static hibernate_state_t hibernate_state = HIBERNATE_STATE_INITIAL; //当前休眠模块所指示的系统当前所处的状态
/** 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;                               //当前休眠结束的时间,当前若不处于休眠状态,则其值为0
/** If we are shutting down, when do we plan finally exit? Set to 0 if
 * we aren't shutting down. */
static time_t shutdown_time = 0;                                    //当前处于将要关闭状态之时,最终的退出时间

  以下的所有全局变量,都是为了能够很好的控制和记录统计流量的周期和各相关时间。在明白了概述中周期运行及流量统计的规则之后,下面这些全局变量的用处应该并不难理解。此处仅仅将其进行简单罗列。他们各自的具体用途,可以从英文注释中简要地弄明白。

/** How many bytes have we read in this accounting interval? */
static uint64_t n_bytes_read_in_interval = 0;
/** How many bytes have we written in this accounting interval? */
static uint64_t n_bytes_written_in_interval = 0;
/** How many seconds have we been running this interval? */
static uint32_t n_seconds_active_in_interval = 0;
/** How many seconds were we active in this interval before we hit our soft
 * limit? */
static int n_seconds_to_hit_soft_limit = 0;
/** When in this interval was the soft limit hit. */
static time_t soft_limit_hit_at = 0;
/** How many bytes had we read/written when we hit the soft limit? */
static uint64_t n_bytes_at_soft_limit = 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;
/** How much bandwidth do we 'expect' to use per minute?  (0 if we have no
 * info from the last period.) */
static uint64_t expected_bandwidth_usage = 0;
/** 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,
           cfg_start_hour = 0,
           cfg_start_min = 0;
/** @} */

2. 接口函数

  2.1. Accounting

  Accounting部分的代码,完全是为了判断系统是否应该进入休眠态或者是否应该被唤醒而服务的,所以他们处于同一个文件之中。

  configure_accounting

  初始化流量统计子模块函数;

  accounting_parse_options

  accounting_is_enabled

  accounting_get_interval_length

  流量统计子模块功能函数:利用配置选项初始化函数;判断是否启用流量统计模块函数;获取流量统计时间区间函数;

  accounting_run_housekeeping  –>  accounting_record_bandwidth_usage

  流量统计子模块维护函数:主要功能是适当时机重启子系统,每隔600秒更行服务器状态并写回硬盘(mark dirty);

  accounting_add_bytes

  流量统计子模块的主要流量统计功能函数:对流量增加的处理;

  getinfo_helper_accounting

  流量统计子模块对外提供的消息询问函数:利用该函数可以得到整个子模块的当前各种状态;

  2.2. Hibernation

  consider_hibernation

  休眠主控函数:调用该函数判断系统当前流量统计的情况是否需要系统进入休眠状态,或要求从休眠状态中被唤醒;

  we_are_hibernating

  休眠判断函数:返回系统当初是否处于休眠态;

  hibernate_begin_shutdown

  关闭系统的函数:系统接收到SIGINT信号时,关闭整个系统(当然包括流量统计模块)。