源码分析(一):子系统及配置项初始化

Tor version: v0.4.1.5

1. tor_run_main

tor_run_main(const tor_main_configuration_t *_tor_cfg*)

新版本的 tor 中 main.c 文件在src/main/

子系统初始化

subsystems_init();

该函数定义如下:

int
subsystems_init(void)
{
  return subsystems_init_upto(MAX_SUBSYS_LEVEL);
}

/**
 * Initialize all the subsystems whose level is less than or equal to
 * <b>target_level</b>; exit on failure.
 **/
int
subsystems_init_upto(int target_level)
{
  // initialize the subsystem_initialized array
  check_and_setup();

  for (unsigned i = 0; i < n_tor_subsystems; ++i) {
    const subsys_fns_t *sys = tor_subsystems[i];
    if (!sys->supported)
      continue;
    if (sys->level > target_level)
      break;
    if (sys_initialized[i])
      continue;
    int r = 0;
    if (sys->initialize) {
      // Note that the logging subsystem is designed so that it does no harm
      // to log a message in an uninitialized state.  These messages will be
      // discarded for now, however.
      log_debug(LD_GENERAL, "Initializing %s", sys->name);
      r = sys->initialize();
    }
    if (r < 0) {
      fprintf(stderr, "BUG: subsystem %s (at %u) initialization failed.\n",
              sys->name, i);
      raw_assert_unreached_msg("A subsystem couldn't be initialized.");
    }
    sys_initialized[i] = true;
  }

  return 0;
}

接着是初始化日志警告安全等级

void
init_protocol_warning_severity_level(void)
{
  atomic_counter_init(&protocol_warning_severity_level);
  set_protocol_warning_severity_level(LOG_WARN);
}

初始化两个变量argcargv

一般 argc 代表运行时传递给 main 函数的命令行参数的个数

**argv[] 则是一个指针数组,存放指向每个参数的指针

int argc = tor_cfg->argc + tor_cfg->argc_owned;
char **argv = tor_calloc(argc, sizeof(char*));
// memcpy为内存拷贝
memcpy(argv, tor_cfg->argv, tor_cfg->argc*sizeof(char*));
if (tor_cfg->argc_owned)
  memcpy(argv + tor_cfg->argc, tor_cfg->argv_owned,
         tor_cfg->argc_owned*sizeof(char*));

给所有的子系统添加 publish/subscribe 关系

/** Install the publish/subscribe relationships for all the subsystems. */
static void
pubsub_install(void)
{
    pubsub_builder_t *builder = pubsub_builder_new();
    int r = subsystems_add_pubsub(builder);
      // tor_assert是tor中的断言机制,类似于其他语言抛出异常?
    tor_assert(r == 0);
    r = tor_mainloop_connect_pubsub(builder); // consumes builder
    tor_assert(r == 0);
}

代码块的 code 表示运行tor_init(),传入的参数为之前定义的 argc 和 argv

具体 tor_init()分析请看第 2 节

{
  int init_rv = tor_init(argc, argv);
  if (init_rv) {
    tor_free_all(0);
    result = (init_rv < 0) ? -1 : 0;
    goto done;
  }
}

2. tor_init

tor_init()是 Tor command-line client 的主入口,返回“0”表示初始化系统成功

char progname[256];
int quiet = 0;

time_of_process_start = time(NULL);
// 初始化连接池
tor_init_connection_lists();
/* Have the log set up with our application name. */
tor_snprintf(progname, sizeof(progname), "Tor %s", get_version());
// 设置应用程序名称
log_set_application_name(progname);

/* Initialize the history structures. */
// 包括history_map()和bw_arrays_init():带宽数组初始化
rep_hist_init();
/* Initialize the service cache. */
// 初始化服务描述符缓存
rend_cache_init();
addressmap_init(); /* Init the client dns cache. Do it always, since it's
                      * cheap. */

/* Initialize the HS subsystem. */
// 缓存Hidden Service子系统
hs_init();

根据命令行输入的参数情况来确定quiet的值,我认为quiet代表一个日志等级

Tor 默认状态下是在控制台打印notice级别以上的日志(errors > warnings > notice),如果配置文件中定义了在其他地方打印日志,则会停止在控制台打印日志

如果添加--hush选项,则告诉 Tor 仅在控制台打印warningserrors级别的日志(可以理解为warnings级别以上)

如果添加--quiet选项,则告诉 Tor 不要在控制台打印任何日志

{
  /* We search for the "quiet" option first, since it decides whether we
   * will log anything at all to the command line. */
  config_line_t *opts = NULL, *cmdline_opts = NULL;
  const config_line_t *cl;
  (void) config_parse_commandline(argc, argv, 1, &opts, &cmdline_opts);
  for (cl = cmdline_opts; cl; cl = cl->next) {
    // 注意strcmp是字符串相等返回0,所以这里代表选项中有--hush,后面亦然
    if (!strcmp(cl->key, "--hush"))
      quiet = 1;
    if (!strcmp(cl->key, "--quiet") ||
        !strcmp(cl->key, "--dump-config"))
      quiet = 2;
    /* The following options imply --hush */
    if (!strcmp(cl->key, "--version") || !strcmp(cl->key, "--digests") ||
        !strcmp(cl->key, "--list-torrc-options") ||
        !strcmp(cl->key, "--library-versions") ||
        !strcmp(cl->key, "--list-modules") ||
        !strcmp(cl->key, "--hash-password") ||
        !strcmp(cl->key, "-h") || !strcmp(cl->key, "--help")) {
      if (quiet < 1)
        quiet = 1;
    }
  }
  config_free_lines(opts);
  config_free_lines(cmdline_opts);
}

/* give it somewhere to log to initially */
switch (quiet) {
  case 2:
    /* no initial logging */
    // quiet最高级别,在控制台不打印任何日志
    break;
  case 1:
    // 仅打印级别 >= warnings的日志
    add_temp_log(LOG_WARN);
    break;
  default:
    // 默认日志级别: >= notice
    add_temp_log(LOG_NOTICE);
}
quiet_level = quiet;

接下来就是从 torrc 初始化配置,具体看第 3 节的分析

// 利用options_init_from_torrc函数从torrc加载配置
int init_rv = options_init_from_torrc(argc,argv);
if (init_rv < 0) {
  // 输入错误的配置项提示
  log_err(LD_CONFIG,"Reading config failed--see warnings above.");
  return -1;
} else if (init_rv > 0) {
  // We succeeded, and should exit anyway -- probably the user just said
  // "--version" or something like that.
  return 1;
}

3. options_init_from_torrc

应该是通过config_parse_commandline函数获取从 command-line 中输入的配置项,存入global_cmdline_optionsglobal_cmdline_only_options

/** Configuration options set by command line. */
static config_line_t *global_cmdline_options = NULL;
/** Non-configuration options set by the command line */
static config_line_t *global_cmdline_only_options = NULL;

...

if (! have_parsed_cmdline) {
  /* Or we could redo the list every time we pass this place.
     * It does not really matter */
  if (config_parse_commandline(argc, argv, 0, &global_cmdline_options,
                               &global_cmdline_only_options) < 0) {
    goto err;
  }
  // 如果从command-line获取配置项成功,标记为1
  have_parsed_cmdline = 1;
}
cmdline_only_options = global_cmdline_only_options;

接下来就是通过config_line_find函数来查找cmdline_only_options中的配置项,类似格式如下:

...
if (config_line_find(cmdline_only_options, "-h") ||
    config_line_find(cmdline_only_options, "--help")) {
  // 对command-line option的操作
  print_usage();
  return 1;
}
...

上述的命令都是列出相关信息的配置项,并不会启动 tor 的主函数

后面的代码是决定 tor 以何种方式运行

Tor 中以枚举的方式列出了其他执行模式:

enum {
  CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD,
  CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG,
  CMD_KEYGEN,
  CMD_KEY_EXPIRATION,
} command;

CMD_RUN_TOR是 Tor 系统真正的执行命令,也是默认的执行模式。

command = CMD_RUN_TOR;
for (p_index = cmdline_only_options; p_index; p_index = p_index->next) {
  if (!strcmp(p_index->key,"--keygen")) {
    command = CMD_KEYGEN;
  } else if (!strcmp(p_index->key, "--key-expiration")) {
    command = CMD_KEY_EXPIRATION;
    command_arg = p_index->value;
  } else if (!strcmp(p_index->key,"--list-fingerprint")) {
    command = CMD_LIST_FINGERPRINT;
  } else if (!strcmp(p_index->key, "--hash-password")) {
    command = CMD_HASH_PASSWORD;
    command_arg = p_index->value;
  } else if (!strcmp(p_index->key, "--dump-config")) {
    command = CMD_DUMP_CONFIG;
    command_arg = p_index->value;
  } else if (!strcmp(p_index->key, "--verify-config")) {
    command = CMD_VERIFY_CONFIG;
  }
}

我们来分析下几个参数选项所对应的模式:

  • CMD_KEYGEN:--keygen: 如果没有 master key,会为 relay 创建一个新的ed25519 master identity key;如果有 master key,则创建临时的签名密钥和证书。Doc:https://trac.torproject.org/projects/tor/wiki/doc/TorRelaySecurity/OfflineKeystor --keygenTor 默认在~/.tor/keys中生成相关的 key。可以通过--DataDirectory指定 keys 的保存路径,注意运行 Tor 的 daemon 必须对该文件夹有读写权限。

  • CMD_KEY_EXPIRATION: --key-expiration:目前只有唯一参数sign,运行“tor -key-expiration sign”将尝试查找您的签名密钥证书,并将在日志和 stdout 中以 ISO-8601 格式输出签名密钥证书的到期时间,格式为“signing-cert-expiry:2017-07-25 08:30:15 UTC”。

  • CMD_LIST_FINGERPRINT: --list-fingerprint:生成密钥,并输出 nickname 和 fingerprint。(我测的时候有点问题)

  • CMD_HASH_PASSWORD: --hash-password: 为 control port 生成一个 hashed password。

  • CMD_DUMP_CONFIG: --dump-config: 我的理解就是输出当前的配置项到控制台(stdout),输出配置有三个级别:short、non-builtin、full
    tor --dump-config

  • CMD_VERIFY_CONFIG: --verfiy-config: Verify the configuration file is valid.

接下来就是读取默认配置文件的配置参数,以及读入命令行输入的配置文件的配置参数(-f参数代表命令行输入配置文件)

// 读取默认配置文件的配置参数
cf_defaults = load_torrc_from_disk(cmdline_only_options, 1);

// 读取命令行内指定的配置文件中的配置参数,通过"-f"指定
const config_line_t *f_line = config_line_find(cmdline_only_options, "-f");
const int read_torrc_from_stdin =
  (f_line != NULL && strcmp(f_line->value, "-") == 0);

if (read_torrc_from_stdin) {
  cf = load_torrc_from_stdin();
} else {
  // 如果找不到,则寻找系统中存在的torrc
  cf = load_torrc_from_disk(cmdline_only_options, 0);
}

接着是利用options_init_from_string所有加载的命令行中的配置参数、默认配置参数或指定配置文件的参数初步初始化系统

// cf_defaults:默认配置文件里的配置项
// cf:-f指定的配置文件
// command:tor的运行模式
// command_arg:command命令后附带的参数
// errmsg:错误信息
retval = options_init_from_string(cf_defaults, cf, command, command_arg, &errmsg);

从这里我发现,tor 加载配置项的覆盖方式是:输入配置文件的配置会覆盖默认配置文件,而命令行配置则会覆盖输入配置文件的配置。

options_init_from_string的具体初始化过程暂不深究


文章作者: 玄霄
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 玄霄 !
评论
 上一篇
VSCode 配置ESLint + Prettier 统一前端代码风格 VSCode 配置ESLint + Prettier 统一前端代码风格
原理 使用 eslint 检查代码 使用 prettier 作为 eslint 的插件来格式化代码 安装 VSCode 插件直接通过商店安装即可,这里附上官网链接: ESLint Prettier 项目中的配置配置 ESLint
2019-09-09
下一篇 
Tor Hidden Service原理初探 Tor Hidden Service原理初探
Tor利用Hidden Service协议中提供的"约会节点"来为除服务提供者之外的其他Tor用户访问被隐藏的服务
2019-08-28
  目录