KRPC简介
KRPC 协议是一种简单的 RPC 机制,由通过 UDP 发送的编码字典组成。发送单个查询数据包并发送单个数据包作为响应。没有重试。共有三种消息类型:查询、响应和错误。对于 DHT 协议,有四种查询:ping、find_node、get_peers 和 announce_peer。
KRPC 消息是一个字典,每个消息都有三个通用的键,并且根据消息的类型还有其他键。
每条消息都有一个键“t”和一个代表交易 ID 的字符串值。该事务 ID 由查询节点生成并在响应中回显,因此响应可能与对同一节点的多个查询相关联。交易 ID 应编码为二进制数字的短字符串,通常 2 个字符就足够了,因为它们涵盖 2^16 个未完成的查询。每条消息还有一个键“y”,带有描述消息类型的单个字符值。“y”键的值是查询的“q”、响应的“r”或错误的“e”之一。每个带有客户端版本字符串的消息中都应包含一个键“v”。后跟两个字符的版本标识符。并非所有实现都包含“v”键,因此客户端不应假定它存在。
{
"t" : "ID", //交易ID——string 编码为二进制数字的短字符串
"y" : "q", //消息类型 "q"查询/"r"响应/"e"错误
"v" : "", //版本标识符(两个字符)
}
接触编码(Contact Encoding)
peers之间的交互信息被编码为一个 6 字节的字符串。也称为“协议 IP 地址/端口信息”(Compact IP-address/port info),
4 字节 IP 地址按网络字节顺序排列,2 字节端口按网络字节顺序排列在末尾。
节点的交互信息被编码为 26 字节的字符串。也称为“协议节点信息”(Compact node info),网络字节顺序的 20 字节节点 ID 具有连接到末尾的协议 IP 地址/端口信息。
查询(Queries)
“y”值为“q”的查询或 KRPC 消息字典包含两个附加键;“q”和“a”。键“q”有一个包含查询方法名称的字符串值。键“a”有一个字典值,其中包含查询的命名参数。
{
"q": "", //查询方法名称——字符串
"a": "", //查询命名参数
}
响应(Responses)
“y”值为“r”的响应或 KRPC 消息字典包含一个附加键“r”。“r”的值是一个包含命名返回值的字典。成功完成查询后发送响应消息。
{
"e":"", //返回值
}
错误(Errors)
“y”值为“e”的错误或 KRPC 消息字典包含一个附加键“e”。“e”的值是一个列表。第一个元素是一个表示错误代码的整数。第二个元素是包含错误消息的字符串。当查询无法完成时发送错误。下表描述了可能的错误代码:
Code | Description |
---|---|
201 | 一般错误 |
202 | 服务器错误 |
203 | 协议错误,例如格式错误的数据包、无效参数或错误令牌 |
204 | 未知的方法 |
{
"e":[
code, //错误代码
message, //错误信息
]
}
DHT查询
所有查询都有一个“id”键和值,其中包含查询节点的节点 ID。所有响应都有一个“id”键和值,其中包含响应节点的节点 ID。
ping
最基本的查询是 ping。”q” = “ping” ping 查询有一个参数,”id” 值是一个 20 字节的字符串,包含按网络字节顺序排列的发送方节点 ID。对 ping 的适当响应具有单个键“id”,其中包含响应节点的节点 ID。
arguments: {"id" : "<querying nodes id>"}
response: {"id" : "<queried nodes id>"}
示例数据包
ping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}
bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re
find_node
查找节点用于查找给定 ID 的节点的联系信息。”q” == “find_node” find_node 查询有两个参数,”id” 包含查询节点的节点 ID,”target” 包含查询者查找的节点的 ID。当一个节点收到一个 find_node 查询时,它应该用一个键“nodes”和一个字符串的值来响应,该字符串包含目标节点或它自己的路由表中 K (8) 个最接近的好节点的紧凑节点信息。
arguments: {"id" : "<querying nodes id>", "target" : "<id of target node>"}
response: {"id" : "<queried nodes id>", "nodes" : "<compact node info>"}
示例数据包
find_node Query = {"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}
bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re
get_peers
获取与 torrent infohash 关联的peer节点。”q” = “get_peers” get_peers 查询有两个参数,”id” 包含查询节点的节点 ID,”info_hash” 包含 torrent 的 infohash。如果查询的节点有 infohash 的对等节点,它们将作为字符串列表在键“值”中返回。每个字符串包含单个对等点的“紧凑”格式对等点信息。如果查询的节点没有 infohash 的对等节点,则返回一个键“nodes”,其中包含查询节点路由表中最接近查询中提供的 infohash 的 K 个节点。在任何一种情况下,“令牌”键也包含在返回值中。令牌值是未来 announce_peer 查询的必需参数。令牌值应该是一个短的二进制字符串。
arguments: {"id" : "<querying nodes id>", "info_hash" : "<20-byte infohash of target torrent>"}
response: {"id" : "<queried nodes id>", "token" :"<opaque write token>", "values" : ["<peer 1 info string>", "<peer 2 info string>"]}
or: {"id" : "<queried nodes id>", "token" :"<opaque write token>", "nodes" : "<compact node info>"}
示例数据包
get_peers Query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe
Response with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}}
bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re
Response with closest nodes = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "nodes": "def456..."}}
bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:re
announce_peer
announce_peer 有四个参数:“id”包含查询节点的节点 ID,“info_hash”包含 torrent 的 infohash,“port”是整数类型的端口,以及响应先前 get_peers 查询收到的“token” . 被查询节点必须验证令牌之前是否已发送到与查询节点相同的 IP 地址。然后,被查询节点应将查询节点的 IP 地址和提供的端口号存储在其对等联系信息存储中的 infohash 下。
有一个名为implied_port
的可选参数,其值为 0 或 1。如果它存在且非零,则端口
参数应被忽略,UDP 数据包的源端口应用作对等端口。这对于可能不知道其外部端口的 NAT 后面的对等点很有用,并且支持 UTP,它们在与 DHT 端口相同的端口上接受传入连接。
arguments: {"id" : "<querying nodes id>",
"implied_port": <0 or 1>,
"info_hash" : "<20-byte infohash of target torrent>",
"port" : <port number>,
"token" : "<opaque token>"}
response: {"id" : "<queried nodes id>"}
示例数据包
announce_peers Query = {"t":"aa", "y":"q", "q":"announce_peer", "a": {"id":"abcdefghij0123456789", "implied_port": 1, "info_hash":"mnopqrstuvwxyz123456", "port": 6881, "token": "aoeusnth"}}
bencoded = d1:ad2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re