Tor 连接目录服务器逻辑

如果要连接到 Tor 网络中,首先需要从目录服务器获得所有路由(中继节点)信息。
从宏观上来看,可以理解成是本地洋葱代理(Onion Proxy, OP)与权威目录服务器(Directory Authorities, DA),但实际使用中,并非这么简单。

备用目录服务器

备用目录服务器(Fallback Directory)
当 Tor 无法连接到任何目录缓存以获取目录信息时,它会尝试硬编码的目录服务器进行连接尝试。
如果 Tor 作为中继运行,每次回尝试与一个权威目录服务器建立连接
而如果以客户端运行,则会尝试多个权威目录服务器以及备用目录服务器,来避免在硬编码权威目录服务器宕机时导致启动失败。并且,其会尽可能使用备用服务器,以减轻权威目录服务器的负载。
备用目录服务器应该是稳定的中继信息,拥有固定的 IP 地址、端口号、公钥,并且开启了DirPort

上面是官方文档中关于这部分的介绍。从中可以得知,客户端在连接 Tor 网络时,实际上并不一定与 DA 建立连接。而这中间就涉及了一个新的概念,备用目录服务器。

如果使用 Stem 来查看 Tor 的配置信息,可以发现输出内容有很大篇幅都是备用目录服务器内容

from stem.control import Controller

 with Controller.from_port(port=9051) as controller:
        controller.authenticate()
        print(controller.get_info("config/defaults"))

部分输出内容:

...
FallbackDir "185.225.17.3:80 orport=443 id=0338F9F55111FE8E3570E7DE117EF3AF999CC1D7 ipv6=[2a0a:c800:1:5::3]:443"
FallbackDir "81.7.10.193:9002 orport=993 id=03C3069E814E296EB18776EB61B1ECB754ED89FE"
FallbackDir "163.172.149.155:80 orport=443 id=0B85617241252517E8ECF2CFC7F4C1A32DCD153F"
FallbackDir "5.200.21.144:80 orport=443 id=0C039F35C2E40DCB71CD8A07E97C7FD7787D42D6"
...

可以看到,Tor 本身已经硬编码了一些备用目录服务器的地址,即使用户不进行配置,仍然可以使用备用目录服务器连接 Tor 网络。

如果从中任意选择一个 IP,在源码中搜索,则可以在src/app/config/fallback_dirs.inc中看到所有硬编码的备用目录服务器信息。
从 Git 提交记录可知,这些信息每年会更新一次,并且硬编码到 Tor 源码中(还说明 2018 年 Tor 源码进行了大规模重构)

 commit 564a9a54a14521ef59275fbb442f92fe385cccea
 Author: David Goulet <dgoulet@torproject.org>
 Date:   Fri Jul 24 14:56:05 2020 -0400
 
    fallbackdir: Remove all three Digitalcourage3  relays
     
     They are about to be shutdown in September.
     
    Signed-off-by: David Goulet <dgoulet@torproject. org>
 
 commit 6f19e67c9805b033e9573bc4805bbecc4cda2310
 Author: David Goulet <dgoulet@torproject.org>
 Date:   Thu Jul 23 09:51:45 2020 -0400
 
     fallbackdir: Update list for 2020
     
     Closes #40061
     
    Signed-off-by: David Goulet <dgoulet@torproject. org>
 
 commit 1dd95278970f9f32d83a31fe73e0258a30523539
 Merge: b0fa1f4fb0 fb977f8cac
 Author: Nick Mathewson <nickm@torproject.org>
 Date:   Mon Jul 1 14:25:12 2019 -0400
 
     Merge branch 'maint-0.2.9' into maint-0.3.5
 
 commit e1273d7d1ba5cced68b0b035176ee20fc3367681
 Merge: c1f86f7492 6506b1ee9f
 Author: Nick Mathewson <nickm@torproject.org>
 Date:   Tue Dec 11 09:41:05 2018 -0500
 
     Merge branch 'maint-0.3.4' into maint-0.3.5
 
 commit 63b4ea22af8e8314dd718f02046de5f4b91edf9d
 Author: Nick Mathewson <nickm@torproject.org>
 Date:   Thu Jul 5 16:31:38 2018 -0400
 
     Move literally everything out of src/or
     
    This commit won't build yet -- it just puts  everything in a slightly
     more logical place.
     
    The reasoning here is that "src/core" will hold  the stuff that every (or
    nearly every) tor instance will need in order to  do onion routing.
    Other features (including some necessary ones)  will live in
    "src/feature".  The "src/app" directory will  hold the stuff needed
    to have Tor be an application you can actually  run.
     
    This commit DOES NOT refactor the former  contents of src/or into a
    logical set of acyclic libraries, or change any  code at all.  That
     will have to come in the future.
     
    We will continue to move things around and split  them in the future,
    but I hope this lays a reasonable groundwork for  doing so.
    

在该文件旁边,还有一个src/app/config/auth_dirs.inc,该文件存储了硬编码的权威目录服务器。也即官方的 1010 台目录服务器:

 "moria1 orport=9101 "
   "v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
   "128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
 "tor26 orport=443 "
   "v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 "
   "ipv6=[2001:858:2:2:aabb:0:563b:1526]:443 "
   "86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D",
 "dizum orport=443 "
   "v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 "
   "45.66.33.45:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
 "Serge orport=9001 bridge "
   "66.111.2.131:9030 BA44 A889 E64B 93FA A2B1 14E0 2C2A 279A 8555 C533",
 "gabelmoo orport=443 "
   "v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 "
   "ipv6=[2001:638:a000:4140::ffff:189]:443 "
   "131.188.40.189:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281",
 "dannenberg orport=443 "
   "v3ident=0232AF901C31A04EE9848595AF9BB7620D4C5B2E "
   "ipv6=[2001:678:558:1000::244]:443 "
   "193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
 "maatuska orport=80 "
   "v3ident=49015F787433103580E3B66A1707A00E60F2D15B "
   "ipv6=[2001:67c:289c::9]:80 "
   "171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",
 "Faravahar orport=443 "
   "v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 "
   "154.35.175.225:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC",
 "longclaw orport=443 "
   "v3ident=23D15D965BC35114467363C165C4F724B64B4F66 "
   "199.58.81.140:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145",
 "bastet orport=443 "
   "v3ident=27102BC123E7AF1D4741AE047E160C91ADC76B21 "
   "ipv6=[2620:13:4000:6000::1000:118]:443 "
   "204.13.164.118:80 24E2 F139 121D 4394 C54B 5BCC 368B 3B41 1857 C413",

验证内容

检查 Tor 启动后建立的连接

由于只需要判断连接的地址,所以完全可以借助 V2Ray 的日志功能研究连接到了哪些 IP

首先,将 Tor 的SOCKS5Proxy到 V2Ray 的端口,然后开启 Tor。
在整个连接过程,只选择了一台服务器51.15.219.22:9111,但很不幸,该服务器并非权威目录服务器,也不是备用目录服务器。使用controller.get_network_statuses(),可以获得所有已知的中继信息(详情可见另一篇文章 『使用 Python Stem 操作 Tor - 获取中继信息』)。
通过对比,可以发现该节点的指纹信息是903A2A7991C838E70FF5A586CD387749B3589669,在官方的检索工具,可以查看其详细信息(实际上上述函数已经提供了足够多的信息)

节点信息节点信息

对连接节点进行猜想并验证

该节点经过搜索,并非硬编码在源码中,因此只能先猜测其来源。
首先,由于其不存在于 Tor 源码中,因此猜测是由于之前建立 Tor 连接的缓存。尽管根据搜索,Tor 本身并不对数据进行缓存,而且在手动执行清除缓存命令,并删除tmp文件夹后结果并未改变。

根据其表现,Tor 在启动后,只建立了这一个连接,而根据 Tor 的实现可知,尽管我们可能并未使用 Tor 电路,但是其仍然会保持多条电路(测试至少是需要确保 55 条电路时刻备用,当要求断开电路后会立即重新创建 55 条电路)。

既然需要创建电路,那么必然需要与入口节点建立连接。通过下述代码查看已建立的电路,可以发现,上面的节点恰好是这 55 条电路中的入口节点。

for circuit in controller.get_circuits():
    print(circuit.id)
    for relay in circuit.path:
        print(" ", relay)

使用controller.get_info("entry-guards")输出所有入口守卫(Entry Guardian)节点,可以看到该节点为第一个入口守卫节点。
在 V2Ray 中将其屏蔽掉,使得 Tor 无法与该节点建立连接。而后重启 Tor。可以发现在发现该节点无法连接后,Tor 与入口守卫列表的第二个节点建立了连接,并且创建了 55 条电路。

因此,综上而言,在曾经建立过连接后,可以认为 Tor 本身已经拥有了路由节点的缓存,其在重启后并不需要与目录服务器建立连接,而是直接从缓存中读取数据。

Tor 节点缓存

使用 "tor files cache" 可以检索到 About the cache files,这里提到 Tor 确实存储了已知节点的信息和公钥,但并未说明其位置。

不过既然存在,就可以使用其他办法来知道它在哪了。使用lsof -c tor可以查看 Tor 打开的文件号(文件号不仅仅是文件,还包括 Socket 等内容)。
从中可以看到其打开了用户目录下的.tor文件夹的一些文件(到这里我才想起来还有这个文件,扒了包括usrvartmp等各个可能存放缓存的文件夹,就忽略了用户目录。🤣一时疏忽,白搜索了 22 个多小时)

直接删掉~/.tor下的所有文件,重启 Tor,可以看到完整的初始化输出内容。

 Aug 05 02:47:43.550 [notice] Tor 0.4.3.5 running on Linux with Libevent 2.1.11-stable, OpenSSL 1.1.1g, Zlib 1.2.11, Liblzma 5.2.5, and Libzstd 1.4.5.
 Aug 05 02:47:43.551 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://www.torproject.org/download/download#warning
 Aug 05 02:47:43.551 [notice] Read configuration file "/etc/tor/torrc".
 Aug 05 02:47:43.556 [notice] Opening Socks listener on 127.0.0.1:9050
 Aug 05 02:47:43.556 [notice] Opened Socks listener on 127.0.0.1:9050
 Aug 05 02:47:43.556 [notice] Opening Control listener on 127.0.0.1:9051
 Aug 05 02:47:43.556 [notice] Opened Control listener on 127.0.0.1:9051
 Aug 05 02:47:43.000 [notice] Parsing GEOIP IPv4 file /usr/share/tor/geoip.
 Aug 05 02:47:43.000 [notice] Parsing GEOIP IPv6 file /usr/share/tor/geoip6.
 Aug 05 02:47:43.000 [notice] Bootstrapped 0% (starting): Starting
 Aug 05 02:47:43.000 [notice] Starting with guard context "default"
 Aug 05 02:47:44.000 [notice] Bootstrapped 3% (conn_proxy): Connecting to proxy
 Aug 05 02:47:44.000 [notice] Bootstrapped 4% (conn_done_proxy): Connected to proxy
 Aug 05 02:47:44.000 [notice] Bootstrapped 10% (conn_done): Connected to a relay
 Aug 05 02:47:46.000 [notice] Bootstrapped 14% (handshake): Handshaking with a relay
 Aug 05 02:47:46.000 [notice] Bootstrapped 15% (handshake_done): Handshake with a relay done
 Aug 05 02:47:46.000 [notice] Bootstrapped 20% (onehop_create): Establishing an encrypted directory connection
 Aug 05 02:47:47.000 [notice] Bootstrapped 25% (requesting_status): Asking for networkstatus consensus
 Aug 05 02:47:47.000 [notice] Bootstrapped 30% (loading_status): Loading networkstatus consensus
 Aug 05 02:47:53.000 [notice] I learned some more directory information, but not enough to build a circuit: We have no usable consensus.
 Aug 05 02:47:54.000 [notice] Bootstrapped 40% (loading_keys): Loading authority key certs
 Aug 05 02:47:54.000 [notice] The current consensus has no exit nodes. Tor can only build internal paths, such as paths to onion services.
 Aug 05 02:47:54.000 [notice] Bootstrapped 45% (requesting_descriptors): Asking for relay descriptors
 Aug 05 02:47:54.000 [notice] I learned some more directory information, but not enough to build a circuit: We need more microdescriptors: we have 0/6766, and can only build 0% of likely paths. (We have 0% of guards bw, 0% of midpoint bw, and 0% of end bw (no exits in consensus, using mid) = 0% of path bw.)
 Aug 05 02:47:56.000 [notice] Bootstrapped 50% (loading_descriptors): Loading relay descriptors
 Aug 05 02:47:57.000 [notice] The current consensus contains exit nodes. Tor can build exit and internal paths.
 Aug 05 02:48:00.000 [notice] Bootstrapped 56% (loading_descriptors): Loading relay descriptors
 Aug 05 02:48:00.000 [notice] Bootstrapped 64% (loading_descriptors): Loading relay descriptors
 Aug 05 02:48:00.000 [notice] Bootstrapped 69% (loading_descriptors): Loading relay descriptors
 Aug 05 02:48:00.000 [notice] Bootstrapped 75% (enough_dirinfo): Loaded enough directory info to build circuits
 Aug 05 02:48:00.000 [notice] Bootstrapped 78% (ap_conn_proxy): Connecting to proxy to build circuits
 Aug 05 02:48:00.000 [notice] Bootstrapped 79% (ap_conn_done_proxy): Connected to proxy to build circuits
 Aug 05 02:48:00.000 [notice] Bootstrapped 85% (ap_conn_done): Connected to a relay to build circuits
 Aug 05 02:48:01.000 [notice] Bootstrapped 89% (ap_handshake): Finishing handshake with a relay to build circuits
 Aug 05 02:48:01.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits
 Aug 05 02:48:01.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit
 Aug 05 02:48:03.000 [notice] Bootstrapped 100% (done): Done

在这个过程中,分别与如下 Socket 进行了通信:

  • 77.247.181.162:443: 备用目录服务器
  • 148.251.190.229:9010: 备用目录服务器
  • 94.156.77.24:443: Tor 在提示节点信息不足以建立电路时,重新向该节点建立了连接,猜测是建立失败的入口守卫
  • 82.64.243.112:9001: 最终使用的入口守卫
  • 81.7.18.7:9001: 备用目录服务器
  • 82.64.243.112:9001: 再此与入口守卫建立连接

禁用备用目录服务器

而再次删除~/.tor下的所有文件,并设置UseDefaultFallbackDirs 0,重启 Tor。
这次与上次启动日志相似,但提示了两次节点数量不足以建立连接。
同时,在 V2Ray 日志可以看到与多个节点建立了连接:

  • 128.31.0.39:9101: 权威目录服务器
  • 171.25.193.9:80: 权威目录服务器
  • 85.226.123.91:443: 一个可用于守卫的节点
  • 149.56.94.217:443: 最终使用的入口守卫
  • 54.38.92.43:9001: 一个可用于守卫的节点
  • 77.120.113.64:443: 一个可用于守卫的节点

可以看到,在不使用备用目录服务器时,OP 会首先与权威目录服务器建立连接,而后如果需要可能会与其推荐的备用目录服务器建立连接(多次测试过程中存在权威目录服务器提供的节点信息不足的情况),而后测试入口守卫节点,并完成电路构建。

屏蔽权威目录服务器

接下来,在 V2Ray 中手动屏蔽 1010 台权威目录服务器

128.31.0.39,
86.59.21.38,
45.66.33.45,
66.111.2.131,
131.188.40.189,
193.23.244.244,
171.25.193.9,
154.35.175.225,
199.58.81.140,
204.13.164.118

如果直接重启,由于存在缓存,可以正常启动 Tor 服务。因此需要再次删除~/.tor

可以看到 Tor 会逐个尝试与权威目录服务器建立连接,但由于 V2Ray 屏蔽了这些 IP 地址,因此全部失败。
随后,Tor 开始警告启动失败,无法建立连接。

如果此时允许 Tor 使用备用目录服务器,那么 Tor 将可以正常建立连接,连入匿名网络。

结论

Tor 的目录服务器已经从原本的具体化概念(1010 台权威目录服务器)变更为一个抽象的概念(任何可以提供目录服务的服务器)。在实际使用中,Tor 可能压根不会与我们所说的权威目录服务器建立连接。在默认情况下,只需要备用目录服务器即可完成获取节点信息的工作。

但这并不意味着可以在网络封禁的情况下连入 Tor 网络。与权威目录服务器类似,备用目录服务器也是公开的,甚至 Tor 中所有中继节点的信息都是公开的。只需要将目前 Tor 6000+6000+ 个中继服务器屏蔽,用户仍然无法连入 Tor 网络。而要解决该问题,Tor 引入了网桥的概念。

网桥类似于一个前置的代理服务,用户通过各种流量伪装技术成功与网桥建立连接,并将所有与 Tor 通信的流量通过网桥传输(与使用代理服务接入 Tor 相同)。但这可能会造成潜在的安全问题,网桥本身可以执行中间人攻击。