Tor源码分析八 — 客户端执行流程(second_elapsed_callback函数)

  在之前的客户端源码分析中,我们讲述了整个客户端的事件集和相关调度规则。每一类事件的激活都有相应的条件,要么是socket可读写,要么是收到信号,要么是定时事件到达,还有手动的事件激活。总而言之,系统中添加的所有事件经过Libevent的调度,使得整个系统有条不紊的运行起来。同时,每个事件均有其对应的事件处理函数,在系统运行起来之后,一旦事件被激活,就会调用相应的回调函数进行处理。

  本文我们着重介绍秒回调事件的事件处理函数second_elapsed_callback。该函数所做的工作非常之多,但是总的说来,是对系统的正常运行的维护和保障。之所以要最先介绍这个函数,是因为新安装的Tor系统在正常启动之后,没有任何事件会先于该秒回调事件被激活,也就是说,系统最先执行的回调函数是秒回调函数。在这个函数中,运行Tor系统的许多必要信息被获取或维护,保障了系统能够在正常地启动,最终为用户提供服务。下面,我们采取代码中加注释的方法,介绍函数的主要过程。英文注释已经描述地很清楚的部分就不再多说了。

1. second_elapsed_callback()

/** Libevent callback: invoked once every second. */
static void
second_elapsed_callback(periodic_timer_t *timer, void *arg) // 本函数的两个参数没有任何用处
{
  static time_t current_second = 0; // 静态的当前时间,每次进入该函数时,该值就是上次执行该函数时的时间;
  time_t now;                       // 真正的当前时间;
  size_t bytes_written;
  size_t bytes_read;
  int seconds_elapsed;
  const or_options_t *options = get_options();
  (void)timer;
  (void)arg;

  n_libevent_errors = 0;

  /* log_notice(LD_GENERAL, "Tick."); */
  now = time(NULL);
  update_approx_time(now);

  /* the second has rolled over. check more stuff. */
  seconds_elapsed = current_second ? (int)(now - current_second) : 0; // 之前进入本函数与本次进入本函数的时间差seconds_elapsed

#ifdef USE_BUFFEREVENTS
  ...... // 我们暂时不讨论使用BUFFEREVENTS的情况;
#else
  // stats_n_bytes_read, stats_prev_n_read, stats_n_bytes_written, stats_prev_n_written
  // 这四个变量是全局变量,分别用来存储系统当前读写的字节数;
  // 开始时,四个全局变量值相等;
  // 当有数据读写的时候,相应增加stats_n_bytes_read,stats_n_bytes_written两个全局变量记录当前读取的字节数;
  // 当本回调函数被调用,利用他们前后数据差来计算两次本回调执行之间所读写的数据量:
  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;
  // 总而言之,此处这么做的最终目的,就是为了每次均可以有效算出两次回调函数执行之间(过去的一秒内)读写数据的总字节数;
#endif

  // 下面两个函数与控制连接相关,暂不讨论
  control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written);
  control_event_stream_bandwidth_used();

  // 该判断成立的条件为:
  // 1.Tor程序运行身份为服务器;
  // 2.网络没有被禁用;
  // 3.前后两次回调函数的执行不是无时间间隔的,也就是的确有时间流逝;
  // 4.系统曾成功开启过链路,也就是说估计系统当前应该处理连接正常状态;
  // 5.系统运行时间大概增加了20分钟;
  if (server_mode(options) &&
      !net_is_disabled() &&
      seconds_elapsed > 0 &&
      can_complete_circuit &&
      stats_n_seconds_working / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT !=
      (stats_n_seconds_working+seconds_elapsed) / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) {
    /* every 20 minutes, check and complain if necessary */
    const routerinfo_t *me = router_get_my_routerinfo(); // 获取自身路由信息结构体;
    if (me && !check_whether_orport_reachable()) {       // 检测自身OR端口是否从外界可达,即OR服务是否有效可用;如果不可用,则产生抱怨= =:
      log_warn(LD_CONFIG,"Your server (%s:%d) has not managed to confirm that "
               "its ORPort is reachable. Please check your firewalls, ports, "
               "address, /etc/hosts file, etc.",
               me->address, me->or_port);
      control_event_server_status(LOG_WARN,
                                  "REACHABILITY_FAILED ORADDRESS=%s:%d",
                                  me->address, me->or_port);
    }

    if (me && !check_whether_dirport_reachable()) {      // 检测自身DIR端口是否从外界可达,即DIR服务是否有效可用;如果不可用,则产生抱怨:
      log_warn(LD_CONFIG,
               "Your server (%s:%d) has not managed to confirm that its "
               "DirPort is reachable. Please check your firewalls, ports, "
               "address, /etc/hosts file, etc.",
               me->address, me->dir_port);
      control_event_server_status(LOG_WARN,
                                  "REACHABILITY_FAILED DIRADDRESS=%s:%d",
                                  me->address, me->dir_port);
    }
  }

/** If more than this many seconds have elapsed, probably the clock
 * jumped: doesn't count. */
#define NUM_JUMPED_SECONDS_BEFORE_WARN 100
  if (seconds_elapsed < -NUM_JUMPED_SECONDS_BEFORE_WARN ||
      seconds_elapsed >= NUM_JUMPED_SECONDS_BEFORE_WARN) { // 此处进行时间异常的处理,也就是当逝去的时间出现不可能的情况之时,进行如下操作:
    circuit_note_clock_jumped(seconds_elapsed);
    /* XXX if the time jumps *back* many months, do our events in
     * run_scheduled_events() recover? I don't think they do. -RD */
  } else if (seconds_elapsed > 0)                          // 无时间异常时,增加系统运行的时间:
    stats_n_seconds_working += seconds_elapsed;

  run_scheduled_events(now); // !!!!!主要的维护函数!!!!!

  current_second = now; /* remember which second it is, for next time */ // 记住本次进入函数的时间,为下一次计算时间差做准备。
}

2. run_scheduled_events()

  这个函数非常之长,此处我们按功能,分段分析。

  下面的主要静态变量的分析说明一定有错误的地方,笔者没有太仔细地去看每个部分的详细说明的情况下就写出了下面的简要介绍,会在后面每个部分的介绍里,详细的准确的说明每个部分的功能和执行大致流程。

/** Perform regular maintenance tasks.  This function gets run once per
 * second by second_elapsed_callback().
 */
static void
run_scheduled_events(time_t now)
{
  // 以下所有变量全为静态变量,每一次变量的修改都会影响下一次函数执行时候变量的值;他们大多都表示下一次需要执行某操作的时间:
  // 每1分钟之内进行的维护:
  static time_t time_to_check_listeners = 0;                        // 每60秒,检查监听端口是否正常监听;
  static time_t time_to_check_descriptor = 0;                       // 每60秒,检查自身描述符是否被修改并需要重新上载;
  static time_t time_to_shrink_memory = 0;                          // 每60秒,尝试缩小系统使用的内存空间;
  static time_t time_to_try_getting_descriptors = 0;                // 每10秒或60秒,根据需要尝试下载所有路由描述符与额外信息;
  static time_t time_to_check_port_forwarding = 0;                  // 每5秒,服务器进行的端口映射检查;
  static time_t time_to_launch_reachability_tests = 0;              // 每10秒,权威目录服务器发起的到Tor网络的连接测试;
  static time_t time_to_next_heartbeat = 0;                         // 每隔一定时间,输出心跳日志;一般为1秒一次;

  // 每10小时之内进行的维护:
  static time_t time_to_check_v3_certificate = 0;                   // 每5分钟,V3权威服务器检查自身V3证书是否过期;
  static time_t time_to_check_ipaddress = 0;                        // 每15分钟,检查地址是否发生改变;
  static time_t time_to_reset_descriptor_failures = 0;              // 每60分钟,重置下载描述符发生错误的次数;
  static time_t time_to_add_entropy = 0;                            // 每60分钟,增加OpenSSL随机数生成器的熵;
  static time_t time_to_write_bridge_status_file = 0;               // 每30分钟,将bridge networkstatus文件写回硬盘;
  static time_t time_to_downrate_stability = 0;                     // 每30分钟,?
  static time_t time_to_save_stability = 0;                         // 每30分钟,?
  static time_t time_to_clean_caches = 0;                           // 每30分钟,清理临时数据;
  static time_t time_to_check_for_expired_networkstatus = 0;        // 每2分钟,检查networkstatus是否过期;
  static time_t time_to_retry_dns_init = 0;                         // 每10分钟,服务器重新尝试初始化DNS服务;
  static time_t time_to_write_stats_files = 0;                      // 每60分钟,将系统数据写回硬盘;
  static time_t last_rotated_x509_certificate = 0;                  // 每120分钟,更新系统TLS上下文;

  // 每1天之内进行的维护:
  static time_t time_to_recheck_bandwidth = 0;                      // 每12小时,检查带宽;
  static time_t time_to_write_bridge_stats = 0;                     // 每24小时,将bridge数据写回硬盘;

  ......
} 

  0. Hibernation, NewNym, Log

  /** 0. See if we've been asked to shut down and our timeout has
   * expired; or if our bandwidth limits are exhausted and we
   * should hibernate; or if it's time to wake up from hibernation.
   */
  //休眠子模块代码位置:Hibernate.c
  //分析该函数时,注意hibernate_state的转换规则。
  consider_hibernation(now);
  //主机是否休眠取决于主机的带宽是否剩余不多,实际上休眠的判断就是判断当前主机读写数据量是否超过一定数值。
  //Tor配置参数内的AccountingMax参数就决定了主机所能读写的数据量。
  //AccountingMax == 0:主机不进行读写数据量的控制操作,也就永远不会休眠;
  //AccountingMax == n:主机每秒关注自身读写数据是否超过一定数值,超过则进入低带宽状态,更甚者进入休眠态。
  //AccountingStart配置参数用于指示系统控制读写量的周期,默认值为一个月。
  //通过以上两个参数,我们可以大致了解,Tor系统监测整个系统的读写数据量,在一个监测周期内,如果数据量过多,则进入低带宽或休眠态。
  //低带宽状态:维护现有连接,不再接受任何连接;(关闭监听端口)
  //休眠状态:关闭所有连接,不再接受任何连接。

  /** 0b. If we've deferred a signewnym, make sure it gets handled
   * eventually. */
  //系统的信号分为两种,一种的普通的系统信号,一种是Tor控制信号。
  //SIGNEWNYM信号就属于Tor控制信号的一种。它是通过控制连接进行信号传递的,并非普通的系统信号传递方式。
  if (signewnym_is_pending &&
      time_of_last_signewnym + MAX_SIGNEWNYM_RATE <= now) {
    log(LOG_INFO, LD_CONTROL, "Honoring delayed NEWNYM request");
    signewnym_impl(now);
  }
  //SIGNEWNYM信号处理子模块代码位置:Main.
  //处理系统收到的更换新假名的信号:一般来说,就是要求之后新建的应用连接使用新的链路。
  //假名更换的周期为MAX_SIGNEWNYM_RATE

  /** 0c. If we've deferred log messages for the controller, handle them now */
  //日志子模块代码位置:Log.c
  flush_pending_log_callbacks();
  //简单地将挂起的日志信息输出到logfile
  //日志模块维护着一个日志文件链表logfile,每当输出一个日志消息时,遍历链表,将消息写到所有的日志文件之中。
  //实际上,系统刚刚启动之时,即在还未解析配置文件和命令行参数之前,系统也需要记录日志信息。
  //此时的日志信息,会被输出到默认输出stdout,而不被输入到日志文件内部。
  //直到系统配置文件及命令行参数被完全解析之后,系统日志模块进入正常工作状态,记录全部日志消息。

  上述代码片段,主要用到的子模块包括:Log.c, Hibernation.c.

  上述部分代码牵涉到系统中的几个主要的子模块。子模块的功能和操作规则均可以通过子模块源代码观察得出。分析子模块代码时,从头文件入手,清晰地分明子模块对外接口以及内部自身实现操作之间的关系与区别。对子模块对外接口的分析,有助于帮助我们理解其提供的主要功能和使用方式。每个子模块会在后面的文章中一一介绍,此处不做过多详细说明。

  1. Onion Key, Cpuworker, TLS, Statistics, DNS

  /** 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys,
   *  shut down and restart all cpuworkers, and update the directory if
   *  necessary.
   */
  //每周更新一次洋葱密钥,洋葱密钥主要用于服务器之间建立TLS连接之时保护DH密钥交换协议的部分参数:g^x。
  //洋葱密钥是路由器自身描述符的一个重要部分,若其发生改变,则需要重新上传描述符。
  //cpuworker子模块主要用于完成系统的多线程操作,一般情况下用于服务器,源代码位置:Cpuworker.c
  if (is_server &&
      get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME < now) {
    log_info(LD_GENERAL,"Rotating onion key.");
    rotate_onion_key();
    cpuworkers_rotate();
    if (router_rebuild_descriptor(1)<0) {
      log_info(LD_CONFIG, "Couldn't rebuild router descriptor");
    }
    if (advertised_server_mode() && !options->DisableNetwork)
      router_upload_dir_desc_to_dirservers(0);
  }

  //获取新的网络路由描述符和额外信息
  if (!options->DisableNetwork && time_to_try_getting_descriptors < now) {
    update_all_descriptor_downloads(now);
    update_extrainfo_downloads(now);
    if (router_have_minimum_dir_info())
      time_to_try_getting_descriptors = now + LAZY_DESCRIPTOR_RETRY_INTERVAL;
    else
      time_to_try_getting_descriptors = now + GREEDY_DESCRIPTOR_RETRY_INTERVAL;
  }

  //重置描述符下载错误信息
  //每种网络共识都有一个网络共识下载状态标记,记录着下载失败的次数等信息;
  //每个路由信息都有一个路由描述符下载状态标记,记录着下载失败的次数等信息;
  //重置这些标志,意味着系统重新尝试下载相关信息。
  if (time_to_reset_descriptor_failures < now) {
    router_reset_descriptor_download_failures();
    time_to_reset_descriptor_failures =
      now + DESCRIPTOR_FAILURE_RESET_INTERVAL;
  }

  //获取网桥描述符信息,要么直接从相关网桥获取,要么从网桥权威认证服务器获取。
  if (options->UseBridges)
    fetch_bridge_descriptors(options, now);

  /** 1b. Every MAX_SSL_KEY_LIFETIME_INTERNAL seconds, we change our
   * TLS context. */
  //重置TLS上下文的工作很简单,不再过多叙述。
  if (!last_rotated_x509_certificate)
    last_rotated_x509_certificate = now;
  if (last_rotated_x509_certificate+MAX_SSL_KEY_LIFETIME_INTERNAL < now) {
    log_info(LD_GENERAL,"Rotating tls context.");
    if (router_initialize_tls_context() < 0) {
      log_warn(LD_BUG, "Error reinitializing TLS context");
      /* XXX is it a bug here, that we just keep going? -RD */
    }
    last_rotated_x509_certificate = now;
    /* We also make sure to rotate the TLS connections themselves if they've
     * been up for too long -- but that's done via is_bad_for_new_circs in
     * connection_run_housekeeping() above. */
  }

  //重新生成随机数种子,令随机数更加随机。
  //生成随机数种子的方式各种各样,主要是利用函数和随机数文件,具体可参看函数详细内容。
  if (time_to_add_entropy < now) {
    if (time_to_add_entropy) {
      /* We already seeded once, so don't die on failure. */
      crypto_seed_rng(0);
    }
/** How often do we add more entropy to OpenSSL's RNG pool? */
#define ENTROPY_INTERVAL (60*60)
    time_to_add_entropy = now + ENTROPY_INTERVAL;
  }

  /** 1c. If we have to change the accounting interval or record
   * bandwidth used in this accounting interval, do so. */
  //执行时间段内的统计操作。
  //统计操作是用于判断系统是否进入休眠状态的重要指标,主要由AccountingMax与AccountingStart两个参数进行统计数量和统计时间的指定。
  //这个部分的操作还是主要与休眠子模块相关,所以代码部分仍然是出于Hibernation.c
  if (accounting_is_enabled(options))
    accounting_run_housekeeping(now);

  //发起测试网络是否可用的操作。主要用于权威目录服务器测试所有其他Tor网络结点是否可达。
  //每10秒测试一组结点,共128组结点,共需大约20分钟时间测试完成。
  //子模块代码处于源文件Dirserv.c
  if (time_to_launch_reachability_tests < now &&
      (authdir_mode_tests_reachability(options)) &&
       !net_is_disabled()) {
    time_to_launch_reachability_tests = now + REACHABILITY_TEST_INTERVAL;
    /* try to determine reachability of the other Tor relays */
    dirserv_test_reachability(now);
  }

  /** 1d. Periodically, we discount older stability information so that new
   * stability info counts more, and save the stability information to disk as
   * appropriate. */
  //Tor系统包含许多统计数据,对他们进行的记录我们称之为reputation history。
  //这些统计数据包含很多种:or,bw,link,conn……
  //此处的统计处理是针对or路由历史信息进行的处理,并将其平均故障间隔时间的统计结果写回硬盘文件中。
  //我们不多做介绍,后边在源文件分析中再详细描述。
  //源文件内容出于Rephist.c
  if (time_to_downrate_stability < now)
    time_to_downrate_stability = rep_hist_downrate_old_runs(now);
  if (authdir_mode_tests_reachability(options)) {
    if (time_to_save_stability < now) {
      if (time_to_save_stability && rep_hist_record_mtbf_data(now, 1)<0) {
        log_warn(LD_GENERAL, "Couldn't store mtbf data.");
      }
#define SAVE_STABILITY_INTERVAL (30*60)
      time_to_save_stability = now + SAVE_STABILITY_INTERVAL;
    }
  }

  /* 1e. Periodically, if we're a v3 authority, we check whether our cert is
   * close to expiring and warn the admin if it is. */
  //每五分钟,作为V3目录服务器的主机检查自身服务器证书是否过时。
  //只检查并报告,不做任何额外处理。
  if (time_to_check_v3_certificate < now) {
    v3_authority_check_key_expiry();
#define CHECK_V3_CERTIFICATE_INTERVAL (5*60)
    time_to_check_v3_certificate = now + CHECK_V3_CERTIFICATE_INTERVAL;
  }

  /* 1f. Check whether our networkstatus has expired.
   */
  //检查自身拥有的网络共识是否过期,若过期则做相应操作。具体的说,该操作就是更新目录信息。
  if (time_to_check_for_expired_networkstatus < now) {
    networkstatus_t *ns = networkstatus_get_latest_consensus();
    /*XXXX RD: This value needs to be the same as REASONABLY_LIVE_TIME in
     * networkstatus_get_reasonably_live_consensus(), but that value is way
     * way too high.  Arma: is the bridge issue there resolved yet? -NM */
#define NS_EXPIRY_SLOP (24*60*60)
    if (ns && ns->valid_until < now+NS_EXPIRY_SLOP &&
        router_have_minimum_dir_info()) {
      router_dir_info_changed();
    }
#define CHECK_EXPIRED_NS_INTERVAL (2*60)
    time_to_check_for_expired_networkstatus = now + CHECK_EXPIRED_NS_INTERVAL;
  }

  /* 1g. Check whether we should write statistics to disk.
   */
  //在一定时间结束之后(一般为一个小时),将系统统计的数据写回硬盘。
  //写回操作主要分成两个子模块处理:Rephist.c,Geoip.c
  //模块具体功能和内容后边会在源码文件的分析中详细描述。
  if (time_to_write_stats_files < now) {
#define CHECK_WRITE_STATS_INTERVAL (60*60)
    time_t next_time_to_write_stats_files = (time_to_write_stats_files > 0 ?
           time_to_write_stats_files : now) + CHECK_WRITE_STATS_INTERVAL;
    if (options->CellStatistics) {
      time_t next_write =
          rep_hist_buffer_stats_write(time_to_write_stats_files);
      if (next_write && next_write < next_time_to_write_stats_files)
        next_time_to_write_stats_files = next_write;
    }
    if (options->DirReqStatistics) {
      time_t next_write = geoip_dirreq_stats_write(time_to_write_stats_files);
      if (next_write && next_write < next_time_to_write_stats_files)
        next_time_to_write_stats_files = next_write;
    }
    if (options->EntryStatistics) {
      time_t next_write = geoip_entry_stats_write(time_to_write_stats_files);
      if (next_write && next_write < next_time_to_write_stats_files)
        next_time_to_write_stats_files = next_write;
    }
    if (options->ExitPortStatistics) {
      time_t next_write = rep_hist_exit_stats_write(time_to_write_stats_files);
      if (next_write && next_write < next_time_to_write_stats_files)
        next_time_to_write_stats_files = next_write;
    }
    if (options->ConnDirectionStatistics) {
      time_t next_write = rep_hist_conn_stats_write(time_to_write_stats_files);
      if (next_write && next_write < next_time_to_write_stats_files)
        next_time_to_write_stats_files = next_write;
    }
    if (options->BridgeAuthoritativeDir) {
      time_t next_write = rep_hist_desc_stats_write(time_to_write_stats_files);
      if (next_write && next_write < next_time_to_write_stats_files)
        next_time_to_write_stats_files = next_write;
    }
    time_to_write_stats_files = next_time_to_write_stats_files;
  }

  /* 1h. Check whether we should write bridge statistics to disk.
   */
  //将网桥数据写回硬盘,这个部分应该是作为网桥的服务器进行数据统计,其他服务器并不做过多操作。
  //作为网桥的服务器要对使用它的客户端进行按国别的统计,以测算某些国家被阻塞的严重性等数据。
  //这个部分的源码也主要处于Geoip.c
  if (should_record_bridge_info(options)) {
    if (time_to_write_bridge_stats < now) {
      if (should_init_bridge_stats) {
        /* (Re-)initialize bridge statistics. */
        geoip_bridge_stats_init(now);
        time_to_write_bridge_stats = now + WRITE_STATS_INTERVAL;
        should_init_bridge_stats = 0;
      } else {
        /* Possibly write bridge statistics to disk and ask when to write
         * them next time. */
        time_to_write_bridge_stats = geoip_bridge_stats_write(
                                           time_to_write_bridge_stats);
      }
    }
  } else if (!should_init_bridge_stats) {
    /* Bridge mode was turned off. Ensure that stats are re-initialized
     * next time bridge mode is turned on. */
    should_init_bridge_stats = 1;
  }

  /* Remove old information from rephist and the rend cache. */
  //清空系统内缓存的各种数据。
  //主要源代码出处:Rephist.c,Rendcommon.c
  if (time_to_clean_caches < now) {
    rep_history_clean(now - options->RephistTrackTime);
    rend_cache_clean(now);
    rend_cache_clean_v2_descs_as_dir(now);
    microdesc_cache_rebuild(NULL, 0);
#define CLEAN_CACHES_INTERVAL (30*60)
    time_to_clean_caches = now + CLEAN_CACHES_INTERVAL;
  }

#define RETRY_DNS_INTERVAL (10*60)
  /* If we're a server and initializing dns failed, retry periodically. */
  //服务器用于重新尝试初始化DNS服务。
  //服务源代码主要出处:DNS.c
  if (time_to_retry_dns_init < now) {
    time_to_retry_dns_init = now + RETRY_DNS_INTERVAL;
    if (is_server && has_dns_init_failed())
      dns_init();
  }

  这个代码片段,主要用到的子模块包括:Rephist.c, Rendcommon.c, Dirserv.c, Dns.c, Geoip.c, Cpuworker.c, Hibernation.c.

  上述代码,在普通的客户端身份下,要么是执行频率较低,要么是身份不正确,因此不会频繁被执行或甚至不会执行。主要在短期内有可能会执行是下载路由器描述符与额外信息的代码段。但是由于系统在第一次执行之时,网络共识还未获取,无从获取路由描述符与额外信息,所以会延后执行。

  2. Route, NetworkConsensus, Dirvote

  /** 2. Periodically, we consider force-uploading our descriptor
   * (if we've passed our internal checks). */

/** How often do we check whether part of our router info has changed in a way
 * that would require an upload? */
#define CHECK_DESCRIPTOR_INTERVAL (60)
/** How often do we (as a router) check whether our IP address has changed? */
#define CHECK_IPADDRESS_INTERVAL (15*60)

  /* 2b. Once per minute, regenerate and upload the descriptor if the old
   * one is inaccurate. */
  //每一分钟和每十五分钟,检查系统带宽和IP地址是否发生改变
  if (time_to_check_descriptor < now && !options->DisableNetwork) {
    static int dirport_reachability_count = 0;
    time_to_check_descriptor = now + CHECK_DESCRIPTOR_INTERVAL;
    check_descriptor_bandwidth_changed(now);
    if (time_to_check_ipaddress < now) {
      time_to_check_ipaddress = now + CHECK_IPADDRESS_INTERVAL;
      check_descriptor_ipaddress_changed(now);
    }
    mark_my_descriptor_dirty_if_too_old(now);
    consider_publishable_server(0);            //作为服务器尝试上次自身新的描述符
    /* also, check religiously for reachability, if it's within the first
     * 20 minutes of our uptime. */
    if (is_server &&
        (can_complete_circuit || !any_predicted_circuits(now)) &&
        !we_are_hibernating()) {
      if (stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) {
        consider_testing_reachability(1, dirport_reachability_count==0);  //作为服务器尝试检测自身ORPort与DirPort是否可达
        if (++dirport_reachability_count > 5)
          dirport_reachability_count = 0;
      } else if (time_to_recheck_bandwidth < now) {
        /* If we haven't checked for 12 hours and our bandwidth estimate is
         * low, do another bandwidth test. This is especially important for
         * bridges, since they might go long periods without much use. */
        const routerinfo_t *me = router_get_my_routerinfo();
        if (time_to_recheck_bandwidth && me &&
            me->bandwidthcapacity < me->bandwidthrate &&
            me->bandwidthcapacity < 51200) {
          reset_bandwidth_test();
        }
#define BANDWIDTH_RECHECK_INTERVAL (12*60*60)
        time_to_recheck_bandwidth = now + BANDWIDTH_RECHECK_INTERVAL;
      }
    }

    /* If any networkstatus documents are no longer recent, we need to
     * update all the descriptors' running status. */
    /* purge obsolete entries */
    networkstatus_v2_list_clean(now);
    /* Remove dead routers. */
    routerlist_remove_old_routers();

    /* Also, once per minute, check whether we want to download any
     * networkstatus documents.
     */
    //系统成功启动最重要的函数。
    //该函数在系统没有网络共识时尝试新建连接对网络共识进行下载。
    //在此之后,系统会针对网络共识内的所有注册服务器进行微描述符的获取,
    //直到获取完毕之时,系统才开始尝试新建第一条用于通信连接的链路,并开始正常接收用户应用程序请求。
    update_networkstatus_downloads(now);
  }

  /** 2c. Let directory voting happen. */
  //作为权威服务器进行的投票操作,Tor系统服务器协议在文件dir_spec.txt中可以找到详细解释,此处略去。
  if (authdir_mode_v3(options))
    dirvote_act(options, now);

  此处略去许多子模块的描述,只是简单的进行操作目的的描述。我们会在后期有时间的情况下,对每一个子模块进行详细地说明。例如,服务器操作,权威服务器操作,桥服务器操作等。在这个部分,我们只要求简单的理解整个秒回调函数的大致目标:保证系统的正常运行,以及定期的系统维护,数据操作等。

  3. Circuit, Stream, Connection

  /** 3a. Every second, we examine pending circuits and prune the
   *    ones which have been pending for more than a few seconds.
   *    We do this before step 4, so it can try building more if
   *    it's not comfortable with the number of available circuits.
   */
  /* (If our circuit build timeout can ever become lower than a second (which
   * it can't, currently), we should do this more often.) */
  //对系统中不符合要求的链路进行删减操作。
  //由于种种原因,一些链路在新建之后,要么一直无法成功建立,要么长期无人使用等,
  //在这些情况下,系统需要对链路进行裁剪,删除无用无效的链路,以便后边重新尝试新建新链路。
  circuit_expire_building();

  /** 3b. Also look at pending streams and prune the ones that 'began'
   *     a long time ago but haven't gotten a 'connected' yet.
   *     Do this before step 4, so we can put them back into pending
   *     state to be picked up by the new circuit.
   */
  //对系统中不符合要求的流进行删减操作。
  //由于一些原因,一些流连接在发出连接请求之后,一直无法得到正常响应,使得连接无法正常开启,
  //在这种情况下,系统需要对流连接进行裁剪,删除无用无效的流连接,以便后边重新尝试新建流连接。
  connection_ap_expire_beginning();

  /** 3c. And expire connections that we've held open for too long.
   */
  //关闭长时间无数据操作的被标记为关闭的连接
  connection_expire_held_open();

  /** 3d. And every 60 seconds, we relaunch listeners if any died. */
  //每分钟进行监听连接的检查,开启非正常关闭的针对需要监听的端口的监听连接。
  if (!net_is_disabled() && time_to_check_listeners < now) {
    retry_all_listeners(NULL, NULL, 0);
    time_to_check_listeners = now+60;
  }

  该部分的代码是针对系统内的流,链路,连接进行的维护操作,以保证整个系统内的各重要功能能正常地顺利地进行。这个部分的内容应该非常容易理解,此处就不再多做说明。

  4. Ideal Circuit

  /** 4. Every second, we try a new circuit if there are no valid
   *    circuits. Every NewCircuitPeriod seconds, we expire circuits
   *    that became dirty more than MaxCircuitDirtiness seconds ago,
   *    and we make a new circ if there are no clean circuits.
   */
  //系统在拥有足够的信息用以建立链路之时,要求至少需要存在一些空闲链接用来接收新请求。
  //默认情况下,要求有2条空闲链接用于处理出口要求为80端口的请求,要求有2条空闲链接用于处理隐藏服务请求。
  //系统第一次开启时,一旦拥有了足够的网络信息之后,此处的函数会被成功调用4次,从而开启2+2条空闲链路用于满足上述需求。
  have_dir_info = router_have_minimum_dir_info();
  if (have_dir_info && !net_is_disabled())
    circuit_build_needed_circs(now);

  /* every 10 seconds, but not at the same second as other such events */
  if (now % 10 == 5)
    circuit_expire_old_circuits_serverside(now);  //关闭未使用过久的非本地发起的链路。

  该部分的代码是针对系统内的链路进行维护操作:保证系统总有足够的空闲链路以满足用户的需求。

  5. Connection

  /** 5. We do housekeeping for each connection... */
  //每条连接的定时维护操作,无难度所以细节略,请大家自行查阅。
  connection_or_set_bad_connections(NULL, 0);
  for (i=0;ioutbuf)
          buf_shrink(conn->outbuf);
        if (conn->inbuf)
          buf_shrink(conn->inbuf);
      });
    clean_cell_pool();
    buf_shrink_freelists(0);
/** How often do we check buffers and pools for empty space that can be
 * deallocated? */
#define MEM_SHRINK_INTERVAL (60)
    time_to_shrink_memory = now + MEM_SHRINK_INTERVAL;
  }

  6. Close Mark Circuit

  /** 6. And remove any marked circuits... */
  //关闭被标记为关闭的链路。
  circuit_close_all_marked();

  7. Hidden Service Descriptor

  /** 7. And upload service descriptors if necessary. */
  //适当地上传自身提供的服务的描述符,需要在配置文件中配置发布HiddenService相关的选项。
  //关于HS服务的细则我们没有进行过说明,此处也不准备进行描述,后边会在分析源文件时适当地进行详细分析。
  if (can_complete_circuit && !net_is_disabled()) {
    rend_consider_services_upload(now);
    rend_consider_descriptor_republication();
  }

  8.Close Mark Connection

  /** 8. and blow away any connections that need to die. have to do this now,
   * because if we marked a conn for close and left its socket -1, then
   * we'll pass it to poll/select and bad things will happen.
   */
  //关闭已经被标记为关闭的连接。
  close_closeable_connections();

  /** 8b. And if anything in our state is ready to get flushed to disk, we
   * flush it. */
  //将系统状态信息等写回硬盘。
  or_state_save(now);

  9.DNS

  /** 9. and if we're a server, check whether our DNS is telling stories to
   * us. */
  //服务器检查DNS,其所处模块也是Dns.c,会在后边找机会进行详细地介绍。
  if (!net_is_disabled() &&
      public_server_mode(options) && time_to_check_for_correct_dns < now) {
    if (!time_to_check_for_correct_dns) {
      time_to_check_for_correct_dns = now + 60 + crypto_rand_int(120);
    } else {
      dns_launch_correctness_checks();
      time_to_check_for_correct_dns = now + 12*3600 +
        crypto_rand_int(12*3600);
    }
  }

  10. Bridge

  /** 10. write bridge networkstatus file to disk */
  //关注Bridge信息的权威服务器在适当的时候将网络内的Bridge信息写回硬盘。
  if (options->BridgeAuthoritativeDir &&
      time_to_write_bridge_status_file < now) {
    networkstatus_dump_bridge_status_to_file(now);
#define BRIDGE_STATUSFILE_INTERVAL (30*60)
    time_to_write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL;
  }

  11. Proxy

  /** 11. check the port forwarding app */
  if (!net_is_disabled() &&
      time_to_check_port_forwarding < now &&
      options->PortForwarding &&
      is_server) {
#define PORT_FORWARDING_CHECK_INTERVAL 5
    /* XXXXX this should take a list of ports, not just two! */
    tor_check_port_forwarding(options->PortForwardingHelper,
                              get_primary_dir_port(),
                              get_primary_or_port(),
                              now);
    time_to_check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL;
  }

  /** 11b. check pending unconfigured managed proxies */
  if (!net_is_disabled() && pt_proxies_configuration_pending())
    pt_configure_remaining_proxies();

  此处部分牵涉到代理及防火墙等方面的操作,笔者暂时不甚了解,暂时略去,后期找机会再详细分析。

  12. Heartbeat

  /** 12. write the heartbeat message */
  //默认情况下1秒钟一次的心跳信息输出。
  if (options->HeartbeatPeriod &&
      time_to_next_heartbeat <= now) {
    log_heartbeat(now);
    time_to_next_heartbeat = now+options->HeartbeatPeriod;
  }

  此处许多部分都没有进行更加详尽地分析,有待后期在源代码文件中进行分别的说明。