本文分析libtorrent中LSD功能的实现源码。LSD,即Local Service Discovery,是BT下载中局域网内客户端寻找、发现节点的方法,简洁有效,易于实现。其基本原理是采用组播的形式发送报文给指定的IP网段,然后接收、解析并判断。
(1)网段
包括IPV4和IPV6:A) 239.192.152.143:6771 (org-local) and B) [ff15::efc0:988f]:6771
(2)announce内容
BT-SEARCH * HTTP/1.1\r\n
Host: <host>\r\n
Port: <port>\r\n
Infohash: <ihash>\r\n
cookie: <cookie (optional)>\r\n
\r\n
\r\n
其中host为(1)中地址,port为自己的端口, infohash提取自torrent/magnet文件,cookie为可选项,用于过滤自己产生的回环消息使用。
(1)报文消息的生成
这里其实很简单,就是一个字符串的替换而已
int render_lsd_packet(char* dst, int const len, int const listen_port
, char const* info_hash_hex, int const cookie, char const* host)
{
TORRENT_ASSERT(len > 0);
return std::snprintf(dst, aux::numeric_cast<std::size_t>(len),
"BT-SEARCH * HTTP/1.1\r\n"
"Host: %s:6771\r\n"
"Port: %d\r\n"
"Infohash: %s\r\n"
"cookie: %x\r\n"
"\r\n\r\n", host, listen_port, info_hash_hex, cookie);
}
(2)报文解析
void lsd::on_announce(udp::endpoint const& from, span<char const> buf)
{
//这里是一个http消息解释器,其实就是分类读取announce报文中的各段消息,可以轻松实现
http_parser p;
bool error = false;
p.incoming(buf, error);
//检测http消息格式,是否有HTTP/1.1\r\n
if (!p.header_finished() || error)
{
#ifndef TORRENT_DISABLE_LOGGING
debug_log("<== LSD: incomplete HTTP message");
#endif
return;
}
//检测是否是BT查询
if (p.method() != "bt-search")
{
#ifndef TORRENT_DISABLE_LOGGING
debug_log("<== LSD: invalid HTTP method: %s", p.method().c_str());
#endif
return;
}
//检测并记录端口
std::string const& port_str = p.header("port");
if (port_str.empty())
{
#ifndef TORRENT_DISABLE_LOGGING
debug_log("<== LSD: invalid BT-SEARCH, missing port");
#endif
return;
}
long const port = std::strtol(port_str.c_str(), nullptr, 10);
if (port <= 0 || port >= int(std::numeric_limits<std::uint16_t>::max()))
{
#ifndef TORRENT_DISABLE_LOGGING
debug_log("<== LSD: invalid BT-SEARCH port value: %s", port_str.c_str());
#endif
return;
}
auto const& headers = p.headers();
//如果有cookie则根据cookie进行判断是否是本地loopback
auto const cookie_iter = headers.find("cookie");
if (cookie_iter != headers.end())
{
// we expect it to be hexadecimal
// if it isn't, it's not our cookie anyway
long const cookie = std::strtol(cookie_iter->second.c_str(), nullptr, 16);
if (cookie == m_cookie)
{
#ifndef TORRENT_DISABLE_LOGGING
debug_log("<== LSD: ignoring packet (cookie matched our own): %x"
, m_cookie);
#endif
return;
}
}
//infohash的判断:相同的infohash则表示下载同一个文件,会加入到节点之中进行后续处理
auto const ihs = headers.equal_range("infohash");
for (auto i = ihs.first; i != ihs.second; ++i)
{
std::string const& ih_str = i->second;
if (ih_str.size() != 40)
{
#ifndef TORRENT_DISABLE_LOGGING
debug_log("<== LSD: invalid BT-SEARCH, invalid infohash: %s"
, ih_str.c_str());
#endif
continue;
}
sha1_hash ih;
aux::from_hex(ih_str, ih.data());
//判断成功,则加入节点中,进行后续peer wire协议
if (!ih.is_all_zeros())
{
#ifndef TORRENT_DISABLE_LOGGING
if (should_log())
{
debug_log("<== LSD: %s:%d ih: %s"
, print_address(from.address()).c_str()
, int(port), ih_str.c_str());
}
#endif
// we got an announce, pass it on through the callback
m_callback.on_lsd_peer(tcp::endpoint(from.address(), std::uint16_t(port)), ih);
}
}
}
(3)除此之外就是发送和接收部分,调用boost库进行处理,实现非常容易,代码也清晰易懂,就不做赘述了