相比于相对简单的Scrape,Announce构造相对麻烦。(嗯,从上篇文章的发出之后,我又尝试了ThinkPHP5、Symfony等架构的测试。经过多次尝试后,决定在某个Swoole的PHP框架上再次开发。之后的文章示意代码依次为准。
发出鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽鸽的声音~
总代码示例
这里贴出的是一个示例的步骤(伪代码),可以看到Tracker的Announce步骤依次如下,对字段的检验和选择,获得种子信息并缓存加快响应、处理请求、生成返回信息。我将依次对这几个部分进行说明。
1 | $this->checkAnnounceFields(&$queries); // 检查请求字段 |
请求字段的检验
虽然贴过很多次与Tracker通信的过程中的HTTP报文,但是为了说明我这里还是再贴一次,并建议打开 BEP 0003 The BitTorrent Protocol Specification 阅读对应文档说明。
1 | 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 |
- 必须字段获取
我们选择'info_hash', 'peer_id', 'port', 'uploaded', 'downloaded', 'left', 'passkey'作为我们的必须字段,当这些字段缺失的时候,应该直接返回错误。其中passkey虽然不是BEP规定的字段,但是是作为Private Tracker必须要的身份证明。示例代码如下:
1 | // Notice: param `passkey` is not require in BEP , but is required in our private torrent tracker system |
并对这些字段值进行校验,检验规则如下:
info_hash、peer_id的长度应为20字节 (PHP中直接使用strlen校验)uploaded、downloaded、left应该是正整数
- 可选(非必须)字段获取
可选字段应该是有默认值存在的,当BT软件在请求中提供的话,就覆盖默认值的字段。示例代码如下:
1 | foreach ([ |
各字段的校验规则如下:
event的值只允许在以下值('started', 'completed', 'stopped', 'paused')中选取或者为空。no_peer_id,compact分别影响返回的响应,其中启用no_peer_id(即&no_peer_id=1)的时候,返回的peer列表中只提供ip和port信息,不提供peer_id信息。而当&compact=1时,tracker应该返回紧凑型响应(见BEP0023 Tracker Returns Compact Peer Lists定义),注意此时同样不返回peer_id信息。numwant表示BT软件希望得到的peer数,常见的请求值有50,100,200。corrupt和key用来标识客户端ip,ipv4,ipv6是用来存储用户ip地址信息的字段。关于用户ip地址的择取我会在后面详细说明。
- 用户ip地址获取
首先我们要知道可以从那些地方获取ip信息。应该是有4块,分别是请求头中记录的remote_ip,以及请求字段中的&ip=,&ipv4=,&ipv6=。那么我们应该采取那个ip地址作为用户ip,是一个很重要的问题。
NexusPHP是直接使用remote_ip并忽略请求字段中的值,这显然是不合理的。因为这样对双栈的用户只记录了他们的其中一个ip(而且极有可能是ipv6地址)。
在BEP0007 IPv6 Tracker Extension中规定了请求字段的&ipv6=以及&ipv4=格式。并给出以下三个示例
Example announce string with 2001::53aa:64c:0:7f83:bc43:dec9 as IPv6 address:
1 | GET /announce?peer_id=aaaaaaaaaaaaaaaaaaaa&info_hash=aaaaaaaaaaaaaaaaaaaa |
Example announce string with [2001::53aa:64c:0:7f83:bc43:dec9]:6882 as IPv6 endpoint:
1 | GET /announce?peer_id=aaaaaaaaaaaaaaaaaaaa&info_hash=aaaaaaaaaaaaaaaaaaaa |
Example announce string with 2001::53aa:64c:0:7f83:bc43:dec9 as IPv6 address and 261.52.89.12 as IPv4 address:
1 | GET /announce?peer_id=aaaaaaaaaaaaaaaaaaaa&info_hash=aaaaaaaaaaaaaaaaaaaa |
鉴于在实际的请求中&ip=字段以及请求头中的remote_ip均有可能为ipv4或者ipv6类型。我们对其采取的是回落策略。即&ipv6= -> &ip=<ipv6> -> remote_ip (ipv6)。额外需要注意的是&ipv6=字段中存储的值有两种形式,一种是IPv6 address,另一种是IPv6 endpoint。对endpoint形式的应该从中提取出ip地址和port端口信息。
- port校验
在最后,我们对port值进行校验。检验原则如下:当event为stopped时,port可以为0,其他情况port值应为0-0xffff(及65535)中整数且不在端口黑名单中。下面是一个可行的黑名单列表:
1 | $portBlacklist = [ |
获取种子信息
这个没什么好说的,根据前面获得的info_hash信息从缓存从获取种子信息,当缓存穿透的时候读数据库中信息。示例代码如下:
1 | $info_hash = $queries["info_hash"]; |
处理请求
处理请求可能是在Private Tracker中最为重要的一块了。在NP中分为三个表users、snatched、peers表的信息更新。
首先应该确定该会话时候在peers表里面存在,如果没有,我们应该在peers表中新填一条记录,并更新snatched表和users表。如果该会话存在的话,因为announce字段的值都是返回总计值的,所以我们应在用户之前记录的基础上,计算累加值作为用户两次announce之间的上传量和下载量,并更新3个表。
在此过程中,我们还可以根据一些信息判断用户是否能够下载或者上传,或者做速度检查。
响应请求
当compact=0&no_peer_id=0时,其返回的json格式如下:
1 | { |
而如果&no_peer_id=1时,peer列表中的peer_id项可以不要。而如果&compact=1时,返回的peer项应该为一个string,且仅存储ipv4用户(每6个字节一个用户),当有ipv6用户时,对应用户信息(每18个字节一个用户)以string存储在peer6中。
因为这里仅涉及了查表操作,故不作详细说明。
仅交代PHP下compact为 inet_pton($peer["ip"]) . pack("n", $peer["port"])。
其中IPv4信息还可以使用pack("Nn", sprintf("%d",ip2long($peer["ip"])), $peer['port']) (NexusPHP使用)
