DeepLearning

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

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 ?

Tor源码分析七 — 握手协议

本节主要讲述Tor系统中所用到的握手协议。握手协议分三层:TCP握手;TLS握手;Tor握手。其中Tor握手又分为三个层次:OR握手;链路建立;流建立。 TCP的三次握手我想应该学计算机方向的朋友无人不知了,所以此处就略去。而TLS是SSL的升级版本,其握手过程与SSLv3几乎一致。同时由于TLS根据客户端的不同握手选择,会有些许握手过程中的差别,我们希望大家能够找到TLS相关的书籍翻阅。此处就不再过多的叙述TLS的握手过程。 本文的重心着眼于描述Tor握手全过程,更甚者是描述整个Tor协议。协议的主要内容包括协议使用的主要数据结构,连接的建立和初始化,连接的协商,链路管理,流管理以及流量控制等部分。在本文描述完全之后,大家会有个对Tor系统中结点之间如何进行交流的一个比较完整的印象。当然,可以说本文中几乎全部内容都来自tor-spec.txt,也就是Tor Protocol Specification。有兴趣的朋友可以参看原文,原文必定比笔者讲的要详细清楚。 1. 系统概述 Tor是一个用以保证用户匿名性的分布式网络。其主要的服务对象是用户所使用的基于TCP连接的应用。在使用Tor系统之时,客户端会选择一系列的结点建立Tor链路。链路中的结点,只知道其前继结点和后继结点,不知道链路中的其他结点信息。这样从某种程度上保证了坏结点存在对链路匿名性的破坏。链路中的数据流是以Cell数据包的形式进行传输的。Cell数据包的负载符合洋葱路由的特性,即由原点出发的负载被多层加密,传输过程中被层层解密;而由出口结点返回的数据在传输过程中层层加密,到原点后由原点一次性解密。而在这个加解密过程中所使用到的密钥,则是由Tor协议提供机制来协商生成的。下面先简要描述系统中所使用到的最常见的密钥: 1)Identity Key(非对称密钥的公钥):系统中的结点均有其自身所对应的ID密钥,该密钥的作用是用以证书签名或标识路由身份。实际上,在前面的文章中我们也提到过,Tor结点根据自身的身份不同,会使用不同的Identity Key。但是,要说明的是,在目录服务器已注册的结点,不可以随意更改其Identity Key,否则就无法正常提供服务。 2)Onion Key(非对称密钥的公钥):系统中提供中继服务的结点,即OR结点,均有一个比ID密钥更短期的一周一换的洋葱密钥。该密钥的主要作用是用来保护OP与OR之间进行DH密钥交换协议的前半部分信息(g^x)的安全性。利用Onion Key,OP与OR之间可以教安全的实行DH密钥交换协议从而协商出洋葱对称密钥。 3)Connection Key(对称密钥):洋葱对称密钥,是OP与各个OR之间协商的临时对称密钥,用来完成上文中叙述的层层加解密功能。 另外,由于结点之间的通信都是由TLS连接来保障的,所以必定会在TLS那个层次中有一对保障TLS通信安全的对称密钥。这并非Tor系统所关心的密钥,所以略去。 注:这个部分的说明和Tor Protocol Specification之中的说明或许有所不同,大家请自行参照更多其它材料来进行分析。最终目标就是理解整个Tor系统的密钥组织结构及其使用。下面会再对整个系统的密码学相关过程做更详细的分析。 上图中给出了Tor系统建立链路的整个过程,下面我们对它进行深入的分析。 1. Alice(OP)开启Tor系统之后,需要建立第一条可用链路,她选择了OR1作为链路第一跳,并与OR1建立起了TLS连接;(TLS的建立与上层系统关系极小) 2. Alice成功与OR1建立TLS连接之后,他们之间的通信就是被TLS连接保障的加密通信。此时,Alice开始执行Tor握手协议第一步:OR握手;(OR握手的过程图中并未给出,而此过程与TLS握手密切相关,我们可以暂且认为OR握手就包含于TLS握手之中) 3. Alice成功与OR1完成OR握手之后,他们相互之间交换了彼此的信息。此时,Alice开始执行Tor握手协议第二步:链路建立; 1)Tor协议中的协议数据交换使用的是固定长度的Cell,以下为Cell的简单框图: 实际上现有版本的Cell结构已经发生了些许改变,但是此处我们用最原始的Cell结构进行说明。下面我们用稍微形式化的方式来描述上述结构: Cell ::= Control Cell | Relay Cell Control Cell ::= Control Header || Control Payload Length = 2B + 1B + 509B = 512B Control Header ::= CircID || Control CMD Length = 2B + 1B = 3B Control Payload ::= Control DATA Length = 509B

Tor源码分析六 — 总体框架图

1. 客户端的总体框图 由于画框图的过程稍微繁琐,所以框图给出的稍慢了些,请大家见谅。同时,框图画的比较急促,一定会有错误的地方,请大家指正。 框图中给出了客户端Tor系统所使用到的最重要的结构体以及其成员变量,有兴趣的朋友可以自行比对,这里就不便多说了。 2. 服务器端的总体框图(2合1) 服务器端的框图将两种身份的服务器所使用到的主要结构体均画了出来。也就是说该框图中展现服务器的两种功能:Relay OR,EXIT OR. 3. 一些额外的说明

Tor源码分析五 — 客户端执行流程(libevent调度)

上一源码分析的小节中,已经将系统的主要流程启动过程介绍完毕。实际上,系统在如此启动之后,就可以通过正确的流程将Tor系统完整的启动起来,为用户提供应用程序代理服务,以达到匿名通信的目标。本节的目标,不是为了详细介绍系统初始化后的正确启动流程,而是介绍系统是如何通过Libevent的调度实现成功启动的。也就是说,本节的重点在于说明Libevent调度机制在Tor系统之中的应用。 上节当中也简要介绍了Libevent的三种应用方式:信号处理;定时处理;网络套接字处理。这三种处理内容对于Libevent来说均是事件,只是事件的属性与回调函数不同而已。Libevent根据自身的调度机制,优先级队列等,来进行事件的调度。事件大多在被创建之时就被加入到Libevent事件集之中,Libevent一旦发现事件被激活,就会执行事件相应的回调函数。而值得说明的是,激活事件的方式根据事件类别的不同而有所区别: 1)signal – 信号处理事件:挂起的信号处理事件在进程接收到相应信号时被激活,信号的发送者可以是用户,也可以是另外一个进程等; 2)time – 定时处理事件:挂起的定时处理事件在定时器到时时被激活,定时器的时间设定会在定时处理事件生成时就指定; 3)socket – 网络套接字处理事件:挂起的网络套接字处理事件主要分两种,分别为读事件和写事件;读事件在相应套接字有数据可读时被激活;写事件比较特殊,它总是处于激活状态的,也就是说,只要套接字是正常可写的,写事件就会不断被激活;因为写事件的情况特殊,所以Tor系统对其的处理是动态地增删,而不是像读事件一样一直放置于事件池内部。利用Libevent的事件池输出调试函数event_base_dump_events可以看到事件池内事件的变动和简单状态,是调试Tor系统的重要手段。(我们讨论非阻塞的读写事件,若读写事件是阻塞的,则Tor系统的单线程执行很容易就会被阻塞,系统就无法正常运行) 0. 系统中的普通事件 此处暂且介绍客户端系统的事件集,服务器端的事件集可以由此作为依据进行猜测。 0.1. 监听事件(socket) — 系统初始化阶段 tor_init() 客户端系统中的监听事件主要分为两种:socket监听;control监听。 以上两种监听事件的目的是不同的:socket监听是监听Tor提供给用户程序的应用端口,接收的是应用请求;control监听是监听Tor提供给用户的控制端口,通过向控制端口传送相应的命令,可以有效控制Tor系统的执行,该部分的控制规则可以在文件control-spec.txt中找到详细的说明。 监听事件的添加是在监听连接创建之后马上执行的,主要处理代码在初始化阶段,入口函数为options_act_reversible。其后主要是通过connection_listener_new函数中的处理来完成事件的新建和添加。connection_start_reading函数完成了事件向事件集中添加的具体操作。在完成了添加之后,一旦进入到主循环的Libevent循环,就将控制权交给Libevent,通过Libevent的机制进行调度,执行提供的回调函数。 此处值得先一提的是,对于所有连接的读事件和写事件,执行的回调函数分别相同:conn_read_callback,conn_write_callback。在后期的分析之中,会着重介绍这两个函数对网络套接字事件所执行的具体处理。 0.2. 信号事件(signal) — 系统无限循环开启前 do_main_loop() 与大多数的Linux软件编程类似,Tor系统进程需要准确地控制进程以接收信号完成相应操作。信号的处理是件相对繁琐的事情,我们此处略去信号的具体处理细节,而把注意力集中在Tor系统是如何利用Libevent来进行信号控制。其实其过程也相对简单,只利用了一个函数: /** Set up the signal handlers for either parent or child. */ void handle_signals(int is_parent) { #ifndef _WIN32 /* do signal stuff only on Unix */ int i; // 定义需要处理的信号 static const int signals[] = { SIGINT, /* do a controlled slow shutdown */ SIGTERM, /* to terminate now */ SIGPIPE, /* otherwise SIGPIPE kills us */ SIGUSR1, /* dump stats */ SIGUSR2, /* go to loglevel debug */ SIGHUP, /* to reload config, retry conns, etc */ #ifdef SIGXFSZ SIGXFSZ, /* handle file-too-big resource exhaustion */ #endif SIGCHLD, /* handle dns/cpu workers that exit */ -1 }; //信号事件集 static struct event *signal_events[16]; /* bigger than it has to be.

Tor源码文件分析 — Circuits全局变量

Tor系统源码中用于控制链路circuit的文件主要有三个,分别是:circuitlist.c,circuituse.c,circuitbuild.c。这三个文件分别主要针对的处理功能与他们的名字相类似,即分别处理链路的组织,使用和建立。在本篇中,我们只介绍主要用于链路部分的全局变量,即存在于这三个文件之中的全局变量的使用。此处不再罗列链路函数,因为链路部分的函数着实不少,尤其是链路建立部分。或许在本文之后会花专门的篇幅描述链路建立以及对链路建立的函数加以说明,不过这不是本篇的主要内容。 0. 全局变量 circuitlist.c circuitlist.c文件主要用于处理链路列表的组织,整理,计数等相关的操作。文件中前两个全局变量如下: /** A global list of all circuits at this hop. */ circuit_t *global_circuitlist=NULL; /** A list of all the circuits in CIRCUIT_STATE_OR_WAIT. */ static smartlist_t *circuits_pending_or_conns=NULL; 第一个全局变量用于维护系统的链路列表。系统链路列表是每个Tor主机上维护的全局链路列表,也就是说所有的链路都挂接在这个链路列表之中。第二个全局变量用于维护系统等待链路的列表。系统等待链路的列表就是系统中正在等待OR连接完成的链路形成的列表。 这两个列表的操作很简单,就是链表的插入和删除。其代码也可以很容易的用sourceinsight进行查找和分析,此处略去。在仔细研究代码之前我们就可以推测,全局链路列表必定是在链路被建立之时被使用到,一旦链路结构体被创建,链路设置完开始状态,就被加入全局链路列表。当然,若链路开始状态为等待OR连接,则也会被加入等待链路列表。这里的前后关系应该并不是那么重要,只是要保证两个列表的一致性:全局链路列表中状态为等待的链路一定会在等待列表中;等待链路列表中的所有链路都应该存在在全局链路列表之中。 文件中后两个全局变量如下:(哈希表的使用请详细查看源代码) /** Map from [orconn,circid] to circuit. */ static HT_HEAD(orconn_circid_map, orconn_circid_circuit_map_t) orconn_circid_circuit_map = HT_INITIALIZER(); HT_PROTOTYPE(orconn_circid_map, orconn_circid_circuit_map_t, node, _orconn_circid_entry_hash, _orconn_circid_entries_eq) HT_GENERATE(orconn_circid_map, orconn_circid_circuit_map_t, node, _orconn_circid_entry_hash, _orconn_circid_entries_eq, 0.6, malloc, realloc, free) /** The most recently returned entry from circuit_get_by_circid_orconn; * used to improve performance when many cells arrive in a row from the * same circuit.

Tor源码文件分析 — Connetion_Edge

所谓的Edge连接主要是形容在链路两端所使用的连接,包括AP连接与EXIT连接两种。Edge连接的存在,主要是为了个应用程序连接提供服务,所以才将两种Edge连接命名为Application Proxy应用代理连接和EXIT出口连接。这两种连接主要出现于链路两端,即链路第一跳与链路最后一跳,分别用于接受应用请求和处理应用请求的发出。因为Edge连接在Tor系统中也具有重要的作用,所以本篇就简单分析关于Edge连接的源码文件connection_edge.c。 因为Edge连接位于链路两端,需要处理的事务比中间结点单纯的传递和记录要繁琐出许多,有很多部分笔者也没有很详细的看过或找出其应用需要,此处必定有很多讲的含糊其辞和不准确的地方,请大家包涵。 0. 全局变量 /** A client-side struct to remember requests to rewrite addresses * to new addresses. These structs are stored in the hash table * "addressmap" below. * 由此处可见,addressmap全局变量的使用,只存在于客户端,其他身份的TOR成员并不使用此全局变量; * * There are 5 ways to set an address mapping: * - A MapAddress command from the controller [permanent] * - An AddressMap directive in the torrc [permanent] * - When a TrackHostExits torrc directive is triggered [temporary] * - When a DNS resolve succeeds [temporary] * - When a DNS resolve fails [temporary] * 由此处可见,除去在配置文件中进行地址映射之外,主要还是靠DNS解析来修改address mapping; * * When an addressmap request is made but one is already registered, * the new one is replaced only if the currently registered one has * no "

Tor源码文件分析 — Connection_OR

作为连接类型中最重要的一种连接,OR连接对OP与OR之间,OR与OR之间的通信负全责。也就是说,OR连接的存在,是解决底层两机之间通信的必要条件。所以,有必要对OR连接的源码文件进行深入分析,并对OR连接先做简要介绍。后期,在介绍Tor系统的全部连接之时,会更加详细地介绍各种连接及他们的作用。 OR连接,即Onion Router连接,是用以连接Tor系统内两结点的连接。该连接的主要功能包括:管理结点间TLS连接;管理活动链接;管理CELL的发送和接收等。OR连接处于Tor系统的最底层,其下层即为TLS层。所以,OR连接负责的主要部分就是可靠的CELL传递以及与其上层链接层的交互。 DIR连接…… 应用层 ------------------------ AP连接,EXIT连接…… ------------------ Circuit链路…… Tor协议层 ------------------ OR连接…… ------------------------ TLS连接 传输层 同时,在此处值得一提的是,Tor系统的应用连接AP,链路Circuit,OR连接三者之间的复用情况:(多AP连接复用Circuit,多Circuit复用OR连接) AP Stream 1 –> AP Stream 2 –> Circuit 1 -> … … 3 –> Circuit 2 -> OR Connection 1 Circuit 3 -> 0. 全局变量 1)orconn_identity_map /** Map from identity digest of connected OR or desired OR to a connection_t * with that identity digest. If there is more than one such connection_t, * they form a linked list, with next_with_same_id as the next pointer.

Tor源码文件分析 — Connection

Tor协议的层次结果经过简要分析可以大致概括为如下框图: DIR连接…… 应用层 ------------------------ AP连接,EXIT连接…… ------------------ Circuit链路…… Tor协议层 ------------------ OR连接…… ------------------------ TLS连接 传输层 而其中的DIR,AP,EXIT,OR等连接均是对理解Tor协议非常重要的部分,所以有必要简要的介绍一下通用连接的源码文件connection.c。 0. 全局变量 // 用于检测IP地址变化的全局变量,引用处极少 // 该部分的全局变量只用于函数 client_check_address_changed /** The last addresses that our network interface seemed to have been * binding to. We use this as one way to detect when our IP changes. * * XXX024 We should really use the entire list of interfaces here. **/ static tor_addr_t *last_interface_ipv4 = NULL; /* DOCDOC last_interface_ipv6 */ static tor_addr_t *last_interface_ipv6 = NULL; /** A list of tor_addr_t for addresses we've used in outgoing connections.

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.