在实质性的进入Tracker内部逻辑之前,让我先水一篇分析性的文章~
框架结构
- NexusPHP
像NP这种较老的框架并没有一个很明显的架构,各项信息杂糅在一起。但从导航栏相关中,大体可以分为以下几个子模块。
以下分类方式仅代表本人意见。
当然,目前各站在这个基础上均增加了一些新的系统,比如(万恶的)勋章系统、发布预告系统、转种系统(含自引用与他站引用)、任务系统、考核系统;对原有系统也有些许扩展,例如Bangumi榜单、AniDB榜单等的添加。
- meanTorrent
meanTorrent的可以在其modules下明确的了解其架构。撰稿时,架构如下:
Tracker请求生命周期
从整个请求流程来看,用户的Bittorrent客户端使用HTTP GET的形式向Tracker服务器发起请求,Tracker服务器根据内部逻辑向数据库及缓存请求请求相关信息,并最终回复给用户。具体流程可见下方简图:
当然上面的肯定过于简单了,没有对Tracker的内部逻辑进行分析,而就Tracker的内部逻辑来看,以NP这个面向流程的框架为例子,简图如下:
而从整个Tracker网络(见下方图片示意)来看,用户在请求后,Tracker并不参与文件交换,同样,也不能真实的获取用户实际交换信息情况。(于是,我又可以就这个方向水一篇SP了)
Tracker分析
在这一手记前,首先先列一下几个相关文件,以方便快速查阅:
- BEP 0003 :The BitTorrent Protocol Specification
- BEP 0007 :IPv6 Tracker Extension
- BEP 0023 :Tracker Returns Compact Peer Lists
- BEP 0048 :Tracker Protocol Extension: Scrape
Private Tracker实现
- ZJUT/NexusPHP/announce.php ,同时也包括其他开源的NP魔改架构,如nwafu-mta/mtpt、zcqian/tjupt
- taobataoma/meanTorren/modules/announce/server/controllers/announces.server.controller.js
- WhatCD/Ocelot/worker.cpp (Gazelle框架背后的主程序)
- mojie126/YoRHa/application/announce/controller/TrackerController.php
首先从最简单的信息查询来看,BT客户端会在还未正式向 /announce
发送请求之前,会向 /scrape
先发送请求(一般在种子 添加或者校验的时候),请求头仅添加种子的info_hash
信息,示例如下:
GET http://nexusphp.localhost/scrape.php?passkey=110099109e337b7e335aa368200d7907c&info_hash=%e4%40%ad%c6%27%db%02%40%f7%c8%ed%9e%7b%aab%a3%e1gFW HTTP/1.1
Host: nexusphp.localhost
User-Agent: uTorrent/2210(25302)
Accept-Encoding: gzip
Connection: Close
关于scrape的相关定义及返回信息在之前列出来的BEP 0048中,根据定义,这次抓取(scrape)
会向用户返回一个字典描述对应种子的完成、未完成以及下载中的情况。本处直接引述定义:
The response to a successful request is a bencoded dictionary containing one key-value pair: the key
files
with the value being a dictionary of the 20-byte string representation of an infohash paired with a dictionary of swarm metadata.
根据NP的实现(见 NexusPHP/scrape.php ),会从param字段中搜索并匹配出来info_hash信息,如果param中没有能匹配出info_hash,则会直接返回站内所有种子的做种信息。(如下图)
以上问题修复patch见 :lock: Not allow empty info_hash when request scrape page by Rhilip · Pull Request #11 · zcqian/tjupt,截止目前,多数站点未对该问题进行修复。
而如果存在info_hash的话,则会对每一个提供的hash值进行数据库查找,如果查询结果不存在,则返回Torrent not registered with this tracker.
的错误,而如果存在,则以一个benc编码的字典返回结果。其json格式如下:
{
"files": {
"xxxxxxxxxxxxxxxxxxxx": {"complete": 11, "downloaded": 13772, "incomplete": 19},
"yyyyyyyyyyyyyyyyyyyy": {"complete": 21, "downloaded": 206, "incomplete": 20}
}
}
此外,一个正常的客户端(例如Transmission 2.94
)会定期向Tracker发送(announce)以下信息,并期望从tracker中获取回复以获取其他peer信息(这里仅指Private Tracker,其他的还有DHT网络、用户交换等方式)。
GET http://nexusphp.localhost/announce.php?passkey=db534098baaaa68bd725aaaae3051518&info_hash=aaaaaaaaaaaaaaaaaaaa&peer_id=-TR2940-lhqkh1jtwjqp&port=21736&uploaded=0&downloaded=0&left=739573325&corrupt=0&key=DDDD2A2B&event=started&numwant=200&compact=1&no_peer_id=1 HTTP/1.1
Host: nexusphp.localhost
User-Agent: Transmission/2.94
Accept-Encoding: gzip
Connection: Close
而tracker则会根据GET字段中的compact
以及no_peer_id
两个字段分别返回不同构造的结果,并设置相应头为
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Pragma: no-cache
当compact=0&no_peer_id=0
时,其返回的json格式如下:
{
"interval": 50,
"min interval": 20,
"complete": 6871,
"incomplete": 3,
"peer": [
{
"ip": "xxx.xxxx.xxx.xxx",
"peer_id": "-TR2940-lhqkh1j31jqp",
"port": 5698,
},
....
]
}
其中, peer_id
视用户Requests字段这一项可以不要,而如果用户申请使用compact的形式返回,那么peer字段则会返回一个string而不是dict。
如果发生错误,则会返回一个错误字段(string),JSON格式内容如下:
{
"failure reason": "Error msg",
}
Bencode编码
在上面的例子中,我都是以json格式的形式来展示tracker返回的Response信息,但实际上,返回的信息是以Bencode的形式进行编码的。关于Bencode的介绍,可以访问Bencode - Wikipedia。这里我们只需要知道一下编码规则就行:
data types | raw | encoded |
---|---|---|
int | -42 | i-42e |
string | 'spam' | 4:spam |
list | ['XYZ', 4321] | l3:XYZi4321ee |
dict | {'XYZ': 4321}' | d3:XYZi4321ee |
这些甚至不需要你自己一个一个拼合,现在有超多的库帮你进行拼合,你只需要构造好相应的字典,直接调用即可,例如:
对于scrape的触发时机描述的不是很准确吧,且没有阐述核心作用。
欢迎您做补充