P2P协议处理流程分析

一、P2P网络应用简述

1.P2P概念

​ 传统如http、ftp等下载文件资源的方式都存在一个共同的问题,即无法缓解服务器节点的带宽压力,这也是基于C/S结构网络应用的通病。P2P(peer-to-peer)下载方式有效的缓解了这一问题,这一解决方式的思路就是将单个资源,分散的存储在多台设备上,分别与这些节点建立连接就可以就近下载,提高下载速度,缓解中心服务器带宽压力。在使用p2p应用时,个人电脑也可以作为peer也就是节点之一,也就是同时作为下载方,和所掌握资源的服务器。

​ 最为常见的P2P应用就是种子文件下载器,如比特彗星、迅雷等软件。

2. torrent概述

​ 要完成P2P协议对于节点下载资源的设想,首先,一个需要下载资源的节点(我们假设称为下载者),需要知道有哪些节点具有资源,因此,下载者在使用迅雷等软件下载文件前,首先需要获取对应的种子文件,

​ 如下图,为下载Ubuntu早期版本镜像的种子文件使用notepad打开后:

image-20230312204751117

​ 理论上,.torrent 文件由Announce(Tracker URL)文件信息两部分组成,前者给出了文件的Tracker 地址,此Tracker也就是下载者得知那些节点具有资源的告知者,如上图所示,可见字符部分给出的就是所需要镜像Tracker的地址和资源的长度、块数等信息。(这里有一个细节,就是torrent文件是由一种特殊的编码方式——Bencode编码的,但是由于本文不探讨编码,故掠过,可参考:https://www.jianshu.com/p/c26de7a04c38)

​ 而后者,文件信息里有以下内容:

  • Info 区:指定该种子包含的文件数量、文件大小及目录结构,包括目录名和文件名;
  • Name 字段:指定顶层目录名字;
  • 每个段的大小:BitTorrent(BT)协议把一个文件分成很多个小段,然后分段下载;
  • 段哈希值:将整个种子种,每个段的 SHA-1 哈希值拼在一起。

​ 下载时,BT 客户端首先解析 .torrent 文件,得到 Tracker 地址,然后连接 Tracker 服务器。Tracker 服务器回应下载者的请求,将其他下载者(包括发布者)的 IP 提供给下载者,下载者就可以使用P2P协议进行连接后,下载资源。

二、协议流程

1.BitTorrent Tracker

​ 上文中说到,通过BT Tracker可以知道当前有哪些终端在同时下载,或者掌握了目标资源。在迅雷上,选择对应种子文件,点击确定下载后,会显示”解析中“,而后开始下载,在此过程中,我们可以捕捉一下流量:

image-20230312212354578

​ 首先,对种子文件所提供的域名进行了DNS解析,获取该域名的ip地址。

​ 建立TCP连接后,发送了如下HTTP请求(Tracker核心数据包):

image-20230312212804322

Tracker GET 请求包含如下参数:

  • info_hash, 20 字节, 将 .torrent 文件中的 info 键对应的值生成的 SHA1 哈希, 该哈希值可作为所要请求的资源的标识符
  • peer_id, 终端生成的 20 个字符的唯一标识符, 每个进行 BT 下载的终端随机生成的 20 个字符的字符串作为其标识符 (终端应在每次开始一个新的下载任务时重新随机生成一个新的 peer_id)
  • IP (可选), 该终端的 IP 地址, 一般情况下该参数没有必要, 因为传输层 (Transport Layer, 如 TCP) 本身可以获取 IP 地址, 但比如 BT 下载器通过 Proxy 与 Tracker 交互时, 该在该字段中设置源端的真实 IP
  • Port, 该终端正在监听的端口 (因为 BT 协议是 P2P 的, 所以每一个下载终端也都会暴露一个端口, 供其它结点下载), BT 下载器首先尝试监听 6881 端口, 若端口被占用被继续尝试监听 6882 端口, 若仍被占用则继续监听 6883, 6884 … 直到 6889 端口, 若以上所有端口都被占用了, 则放弃尝试
  • uploaded, 当前已经上传的文件的字节数 (十进制数字表示)
  • downloaded, 当前已经下载的文件的字节数 (十进制数字表示)
  • left, 当前仍需要下载的文件的字节数 (十进制数字表示)
  • numwant, 可选, 希望 BT Tracker 返回的 peer 数目, 若不填, 默认返回 50 个 IP 和 Port
  • event, 可选, 该参数的值可以是 started, completed, stopped, empty 其中的一个, 该参数的值为 empty 与该参数不存在是等价的, 当开始下载时, 该参数的值设置为 started, 当下载完成时, 该参数的值设置为 completed, 当下载停止时, 该参数的值设置为 stopped

请求成功时, BT Tracker 返回的字典中应含有以下 Key(这里记录由于我的失误,第一次没有抓到这个包,重新抓的时候,迅雷大概是在本地保存了某种缓存机制,未进行Tracker请求,因此没有抓到这个包,非常恼火,然后我换了一个种子资源,跟踪http流应该就能找到,但我并不知道为啥好像我这个不太对劲,猜测是未解析成功,后续使用了其他方式进行解析)

image-20230312215157309

但是理论上,tracker应该做如下回复:

  • warnging message, 当发生非致命性错误时, Tracker 返回的可读的警告信息
  • interval, 对应的 Value 是终端在下一次请求 BT Tracker 前应等待的时间 (以秒为单位)
  • min interval, 对应的 Value 是终端在下一次请求 BT Tracker 前的最短等待时间 (以秒为单位)
  • complete, 对应的 Value 表明当前已完成整个资源下载的 peer 的数量
  • incomplete, 对应的 Value 表明当前未完成整个资源下载的 peer 的数量
  • peers, 对应的 Value 是一个字典的列表, 即列表的每一个元素都是一个字典, 每个字典包含有两个 Key, 分别为:
    • peer id, peer 结点的 Id
    • IP, peer 结点的 IP 地址
    • Port, peer 结点的端口

后续进行了查询,上述规则描述的是最基本的Torrent协议,在经过一定的更新和调整后,市面上大多数软件(如迅雷)都已不再符合基础的传输标准了。在P2P的基础上,可能引入TLS等协议保证安全性。

而后,建立基于TLS1.2的加密通信开始传输:

image-20230312212532162

2. 数据传输策略

数据的分块

​ 传输过程中,完整的数据会被分割成更小的片段,这些碎片有一个固定的大小,以保证tracker能够掌握谁拥有哪些数据碎片。通过对于数据的分块处理,下载者可以通过对应块的哈希值检查数据的完整性。这些哈希码作为 “metainfo file”的一部分被储存起来,在前文讨论种子文件时,我们已经提到过。

​ 在同一个种子文件中,每一个碎片,也就是piece的大小保持不变,但是最后的一个碎片的大小是不规则的。piece大小的分配取决于数据量。太大的piece会导致下载效率低下(同时,一旦其中一块损坏,必须重新下载该块);如果片段大小太小,则需要运行更多的哈希检查,使得效率降低,种子文件也变得臃肿。因此,一般来讲,应当通过piece大小的选择,使得metainfo文件不大于50-75kb。比较常见的碎片大小是256kb、512kb和1mb。

img

​ 如上图,一个1.4mb大小的文件配分为六块,其中前五块为5*256kb,最后一块为120kb。

peer的选取

​ Peer们不停的以队列的方式下载所需要的文件块,因此,tracker也需要同时持续的回复peer,内容为掌握对应piece得peer列表。那么进一步的,本地的peer客户端就需要根据策略,向对应的peer获取对应piece。策略如下:

  • Random First Piece:当首次开始下载,由于当前peer没有任何东西可以进行上传(即和其他peer共享),可直接随机选取一个piece进行下载。
  • Rarest First:首次下载完成后,执行”rarest first“即稀有度优先政策进行下载。
  • Endgame Mode:当下载马上要被完成时,为了避免后续有传输较慢的piece,则会同时向所有已知掌握了piece的peer发送请求。

稀有度优先策略(rarest first):当一个peer要下载piece时,要选取当前有最少peer持有的piece,即最稀有的piece。这也就意味着,最常见,也就是最多peer持有的piece的下载任务将会被留到最后,而最稀有的资源将首先被复制。这样做的原因是,一切种子文件的下载,在最开始都是由一个peer节点掌握了所有piece的(这很好理解,就是当还没有进行过下载任务前,种子文件一开始肯定只存在于一台终端上),在这时,为了避免所有下载者peer都尝试访问同一个piece产生的瓶颈,可让他们访问不同的piece。在之后,原始peer节点,也就是一开始掌握了所有piece的节点可能因为带宽占用问题会断开连接,彼时,如果不采用稀有度优先策略,则极有可能由于某个稀有的piece未及时遭到复制,导致该piece永久的丢失。

choking 上传阻塞策略

​ 当一个peer收到来自另一个节点的请求时,他有权拒绝该片段,该拒绝策略被称为”Choking“。Choking较为常见的发生情况是,一个peer节点默认只接收一定数量的上传请求,以保证性能。

img

如上图所示,上面的节点已经建立了多个连接上传信息,故此没标记为choked节点,拒绝来自下面节点的连接请求,一般来说max_uploads都被设置为4。

3.BitTorrent Peer Protocol

协议基础信息

一些关于P2P协议的基础知识:

  1. 从五层模型的角度讲,BitTorrent协议属于第五层,也就是应用层的协议,该协议基于tcp建立的传输层基础,常用端口为6881-6889。(注意,很多网上教程称可以基于udp协议,但实际官网文档提到不能使用udp协议)image-20230313234412293
  2. 所有正在下载同一资源的结点都是对等的,结点之间相互建立的连接也是对等的,对等结点建立的连接的数据传输方向是双向的,即数据可以由任何一端发往另一端。
  3. 该协议规定的发送节点和接收节点之间是对等的该协议的安全性是基于SHA1哈希算法的,接收每接收到一个完整的 piece 之后,计算该 piece 的 SHA1 哈希并与 .torrent 文件中的 info 对应的字典的 piece 对应的 Value 的相应分段做对比, 若哈希值相等, 则说明该分片传输成功,保证了传输过程中的不可篡改性。
  4. 直接传输一个piece太大,故传输过程中, piece 会进一步分为固定大小的 slice, 对等结点使用 slice 作为最基本的数据传输单位。

​ 对等结点在建立完传输层连接 (如 TCP) 之后, 便开始进行 Peer Protocol 的握手,。

握手协议

握手协议内容如下:

  • pstrlen, 该值固定为 19 (十进制格式, 使用 4 字节大端字节序)
  • pstr, 该值为 “BitTorrent protocol”字符串 (BitTorrent 协议的关键字)
  • reserved, BT 协议的保留字段, 用于以后扩展用, 一般将这 8 字节全部设置为 0, 某些 BT 客户端没有正确实现协议或者使用了某种扩展而发送了不全为 0 的握手信息, 此时忽略即可
  • info_hash, 与请求 BT Tracker 时发送的 info_hash 参数值相同
  • peer_id, 与请求 BT Tracker 时发送的 peer_id 参数值相同

当对方收到握手信息后,应该按照上述内容,回复一个一样格式的握手,若双方peer都对于info_hash和peer_id校验成功,则连接建立成功。

传输过程

​ 数据的通用格式为 <length prefix><type id><payload>, 其中 length prefix 顾名思义是消息的长度 (即 len(type id) + len(payload)), type id (十进制整数) 指示消息的类型, 特别地, length prefix 为 0 (此时消息没有 type id 也没有 payload) 代表 Keep Alive 消息, BT 协议标准规定 Keep Alive 消息每 2 min 发送一次, 接收端忽略该消息即可, 对于其它类型的消息, 其对应的 type id 如下:

Prefix Message Structure Additional Information
0 choke <len=0001><id=0> Fixed length, no payload. This enables a peer to block another peers request for data.
1 unchoke <len=0001><id=1> Fixed length, no payload. Unblock peer, and if they are still interested in the data, upload will begin.
2 interested <len=0001><id=2> Fixed length, no payload. A user is interested if a peer has the data they require.
3 not interested <len=0001><id=3> Fixed length, no payload. The peer does not have any data required.
4 have <len=0005><id=4><piece index> Fixed length. Payload is the zero-based index of the piece. Details the pieces that peer currently has.
5 bitfield <len=0001+X><id=5><bitfield> Sent immediately after handshaking. Optional, and only sent if client has pieces. Variable length, X is the length of bitfield. Payload represents pieces that have been successfully downloaded.
6 request <len=0013><id=6><index><begin><length> Fixed length, used to request a block of pieces. The payload contains integer values specifying the index, begin location and length.
7 piece <len=0009+X><id=7><index><begin><block> Sent together with request messages. Fixed length, X is the length of the block. The payload contains integer values specifying the index, begin location and length.
8 cancel <len=13><id=8><index><begin><length> Fixed length, used to cancel block requests. payload is the same as ‘request’. Typically used during ‘end game’ mode.

参考文献: