Tor源码文件分析 — Main

  Main文件是Tor系统的主要执行函数所处文件,内容偏多,但是较于其他底层处理函数所在的文件,也算较少。所以这里做简要的分析,其中删去很多不常见的,不重要的函数,大家可以自行在源文件内部查看。

1. 全局变量

  全局变量在文件头部定义,每个全局变量都已经有比较详尽的英文分析和解释,这里再做简单的罗列。

  1)系统(先前)已读与已写字节数以及用于流量控制的令牌桶全局变量;(先前与当前已读写字节数的区别与refill回调函数相关)

  令牌桶用于控制整个系统的流量,经过初始化,减少,增加等操作(当读写相应的令牌数不够时,系统或连接会停止读或写):

// 初始化令牌桶令牌数
/** Initialize the global read bucket to options-\>BandwidthBurst. */
void
connection_bucket_init(void)
{
  const or_options_t *options = get_options();
  /* start it at max traffic */
  global_read_bucket = (int)options->BandwidthBurst;
  global_write_bucket = (int)options->BandwidthBurst;
  if (options->RelayBandwidthRate) {
    global_relayed_read_bucket = (int)options->RelayBandwidthBurst;
    global_relayed_write_bucket = (int)options->RelayBandwidthBurst;
  } else {
    global_relayed_read_bucket = (int)options->BandwidthBurst;
    global_relayed_write_bucket = (int)options->BandwidthBurst;
  }
}
// 减少令牌桶令牌数
/** We just read num_read and wrote num_written bytes
 * onto conn. Decrement buckets appropriately. */
static void
connection_buckets_decrement(connection_t *conn, time_t now,
                             size_t num_read, size_t num_written)
{
  ......

  if (connection_counts_as_relayed_traffic(conn, now)) {
    global_relayed_read_bucket -= (int)num_read;
    global_relayed_write_bucket -= (int)num_written;
  }
  global_read_bucket -= (int)num_read;
  global_write_bucket -= (int)num_written;

 ......
}
// 增加令牌桶令牌数
/** Time has passed; increment buckets appropriately. */
void
connection_bucket_refill(int milliseconds_elapsed, time_t now)
{
  ......

  /* refill the global buckets */
  connection_bucket_refill_helper(&global_read_bucket,
                                  bandwidthrate, bandwidthburst,
                                  milliseconds_elapsed,
                                  "global_read_bucket");
  connection_bucket_refill_helper(&global_write_bucket,
                                  bandwidthrate, bandwidthburst,
                                  milliseconds_elapsed,
                                  "global_write_bucket");
  connection_bucket_refill_helper(&global_relayed_read_bucket,
                                  relayrate, relayburst,
                                  milliseconds_elapsed,
                                  "global_relayed_read_bucket");
  connection_bucket_refill_helper(&global_relayed_write_bucket,
                                  relayrate, relayburst,
                                  milliseconds_elapsed,
                                  "global_relayed_write_bucket");
 ......
}

  先前已读写字节数全局变量的具体赋值处于函数second_elapsed_callback之中,其他地方没有引用:

  bytes_read = (size_t)(stats_n_bytes_read - stats_prev_n_read);
  bytes_written = (size_t)(stats_n_bytes_written - stats_prev_n_written);
  stats_prev_n_read = stats_n_bytes_read;
  stats_prev_n_written = stats_n_bytes_written;

  当前已读写字节数全局变量的具体赋值处于函数refill_callback之中,其他地方没有引用:

  bytes_written = stats_prev_global_write_bucket - global_write_bucket;
  bytes_read = stats_prev_global_read_bucket - global_read_bucket;

  stats_n_bytes_read += bytes_read;
  stats_n_bytes_written += bytes_written;

  2)系统启动时间和运行时间;

  系统启动时间初始化于Tor系统初始化阶段,未曾修改过:

tor_init(int argc, char *argv[])
{
  ......
  time_of_process_start = time(NULL);
 ......
}

  系统运行时间在函数second_elapsed_callback中随时间增加;在系统发生重要变故的时候会被重新设置为0,例如hibernation,Ip改变等情况。

  3)启动下次DNS检查的时间;

  只有在系统配置为服务器时候才使用,改变部分在函数run_scheduled_events之中,此处略去。

  4)newnym相关变量;

  newnym信号相关变量均于信号处理函数内部进行改变,入口大致为:

/** Do the work of acting on a signal received in sig */
void
process_signal(uintptr_t sig)
{
  switch (sig)
  {
  ....
    case SIGNEWNYM: {
      time_t now = time(NULL);
      if (time_of_last_signewnym + MAX_SIGNEWNYM_RATE > now) {
        signewnym_is_pending = 1;
        log(LOG_NOTICE, LD_CONTROL,
            "Rate limiting NEWNYM request: delaying by %d second(s)",
            (int)(MAX_SIGNEWNYM_RATE+time_of_last_signewnym-now));
      } else {
        signewnym_impl(now);
      }
      break;
    }
  ......
}

  5)三大连接链表全局变量:全连接链表,可关闭的连接链表,活动的linked连接列表;(linked连接的含义,在Tor源码分析中会提到)

  这三个连接链表因为引用非常多,不可能贴出其主要引用位置。大致的可以说,当系统出现相关连接时,会被加入到相关连接列表中:全部的,可关闭的,活动的linked。当系统删除相关连接时,这些对应的连接就会被从相关连接列表中删除。这个部分是Tor系统的核心控制部分,会在系统代码分析中经常遇到,所以此处就粗略描述。

  6)Libevent循环指示变量;

  call_loop_once变量用于指示Libevent循环是否阻塞。如果该值为1,则Libevent会不阻塞地执行被激活的事件处理函数,当没有需要执行的处理函数时,会返回。如果该值为0,则Libevent会阻塞地执行被激活的事件处理函数,即没有需要执行的处理函数时,阻塞不返回。在后者的情况下,Tor系统为了处理特定的事件而需要推出loop循环,则需要调用特定的tor_event_base_loopexit函数来让主循环暂时退出:

do_main_loop
{
    ......
    /* All active linked conns should get their read events activated. */
    SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn,
                      event_active(conn->read_event, EV_READ, 1));
    called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0;

    update_approx_time(time(NULL));

    /* poll until we have an event, or the second ends, or until we have
     * some active linked connections to trigger events for. */
    loop_result = event_base_loop(tor_libevent_get_base(),
                                  called_loop_once ? EVLOOP_ONCE : 0);
    ......
}

  7)链路可用性指示变量。

  以下为所有全局变量的罗列:

#ifndef USE_BUFFEREVENTS
int global_read_bucket; /**< Max number of bytes I can read this second. */
int global_write_bucket; /**< Max number of bytes I can write this second. */

/** Max number of relayed (bandwidth class 1) bytes I can read this second. */
int global_relayed_read_bucket;
/** Max number of relayed (bandwidth class 1) bytes I can write this second. */
int global_relayed_write_bucket;
/** What was the read bucket before the last second_elapsed_callback() call?
 * (used to determine how many bytes we've read). */
static int stats_prev_global_read_bucket;
/** What was the write bucket before the last second_elapsed_callback() call?
 * (used to determine how many bytes we've written). */
static int stats_prev_global_write_bucket;
#endif

/* DOCDOC stats_prev_n_read */
static uint64_t stats_prev_n_read = 0;
/* DOCDOC stats_prev_n_written */
static uint64_t stats_prev_n_written = 0;

/* XXX we might want to keep stats about global_relayed_*_bucket too. Or not.*/
/** How many bytes have we read since we started the process? */
static uint64_t stats_n_bytes_read = 0;
/** How many bytes have we written since we started the process? */
static uint64_t stats_n_bytes_written = 0;
/** What time did this process start up? */
time_t time_of_process_start = 0;
/** How many seconds have we been running? */
long stats_n_seconds_working = 0;
/** When do we next launch DNS wildcarding checks? */
static time_t time_to_check_for_correct_dns = 0;

/** How often will we honor SIGNEWNYM requests? */
#define MAX_SIGNEWNYM_RATE 10
/** When did we last process a SIGNEWNYM request? */
static time_t time_of_last_signewnym = 0;
/** Is there a signewnym request we're currently waiting to handle? */
static int signewnym_is_pending = 0;
/** How many times have we called newnym? */
static unsigned newnym_epoch = 0;

/** Smartlist of all open connections. */
static smartlist_t *connection_array = NULL;
/** List of connections that have been marked for close and need to be freed
 * and removed from connection_array. */
static smartlist_t *closeable_connection_lst = NULL;
/** List of linked connections that are currently reading data into their
 * inbuf from their partner's outbuf. */
static smartlist_t *active_linked_connection_lst = NULL;
/** Flag: Set to true iff we entered the current libevent main loop via
 * loop_once. If so, there's no need to trigger a loopexit in order
 * to handle linked connections. */
static int called_loop_once = 0;

/** We set this to 1 when we've opened a circuit, so we can print a log
 * entry to inform the user that Tor is working.  We set it to 0 when
 * we think the fact that we once opened a circuit doesn't mean we can do so
 * any longer (a big time jump happened, when we notice our directory is
 * heinously out-of-date, etc.
 */
int can_complete_circuit=0;

2.功能函数(加*号的为极重要函数)

  dumpmemusage

  dumpstats

  记录内存使用情况与系统当前状态,输出至log;

  conn_read_callback*

  conn_write_callback*

  second_elapsed_callback*    ->    run_schedule_events    ->    run_connection_housekeeping

  refill_callback

  signal_callback    ->    process_signal

  读写、秒定时器、令牌桶填充以及信号处理回调函数;

  conn_close_if_marked

  关闭连接池中已被标记为关闭的连接;

  connection_should_read_from_linked_conn*

  connection_start_reading_from_linked_conn*

  connection_stop_reading_from_linked_conn*

  判断linked连接的可读性,并对该连接进行读写准备操作;(所谓的读写准备操作,就与上文中所提到的活动的linked连接链表有直接关系。实际上就是对活动linked连接链表的增删操作。活动linked链表会在每次程序主循环时被检测,激活其中所有连接进行读写调度。

  connection_add                          ->

  connection_add_connecting    ->  connection_add_impl*

  向连接池中添加新连接,并为该连接生成读写事件,但并不将事件加入调度池;(->代表调用,下同)

  connection_unlink    ->    connection_remove    ->    connection_unregister_events

  从右到左依次为将连接的读写事件从调度池中删除,并将连接从连接池中删除,最终将连接完全释放;

  connection_watch_events*    ->    connection_start_reading,connection_start_writing,connection_stop_reading,connection_stop_writing

  connection_is_reading

  connection_is_writing

  针对连接的读写事件加入调度池或调出调度池的操作和读写判断;普通连接的操作通过调度池完成,linked连接的操作通过活动linked连接链表完成。

  

  try_locking

  have_lockfile

  release_locking

  全局加锁文件加解锁控制;

  tor_main    ->    tor_init*,do_main_loop*,do_list_fingerprint,do_hash_password

  Tor程序主函数执行分支流程。