2010-10-15 21:55:56 +00:00
|
|
|
/*
|
2018-04-14 22:53:45 +03:00
|
|
|
* Bittorrent Client using Qt and libtorrent.
|
2019-08-08 23:21:07 +08:00
|
|
|
* Copyright (C) 2019 Mike Tzou (Chocobo1)
|
2015-01-28 12:03:22 +03:00
|
|
|
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
2017-09-07 03:00:04 +03:00
|
|
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
2010-10-15 21:55:56 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
* In addition, as a special exception, the copyright holders give permission to
|
|
|
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
|
|
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
|
|
|
* and distribute the linked executables. You must obey the GNU General Public
|
|
|
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
|
|
|
* modify file(s), you may extend this exception to your version of the file(s),
|
|
|
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
|
|
|
* exception statement from your version.
|
|
|
|
*/
|
|
|
|
|
2018-04-14 22:53:45 +03:00
|
|
|
#include "tracker.h"
|
|
|
|
|
2010-10-15 21:55:56 +00:00
|
|
|
#include <libtorrent/bencode.hpp>
|
2015-04-19 18:17:47 +03:00
|
|
|
#include <libtorrent/entry.hpp>
|
2010-10-15 21:55:56 +00:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
#include <QHostAddress>
|
|
|
|
|
|
|
|
#include "base/exceptions.h"
|
|
|
|
#include "base/global.h"
|
|
|
|
#include "base/http/httperror.h"
|
2015-09-25 11:10:05 +03:00
|
|
|
#include "base/http/server.h"
|
2019-08-08 23:21:07 +08:00
|
|
|
#include "base/http/types.h"
|
|
|
|
#include "base/logger.h"
|
2017-09-07 03:00:04 +03:00
|
|
|
#include "base/preferences.h"
|
2010-10-15 21:55:56 +00:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
namespace
|
2010-10-15 21:55:56 +00:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
// static limits
|
|
|
|
const int MAX_TORRENTS = 10000;
|
|
|
|
const int MAX_PEERS_PER_TORRENT = 200;
|
|
|
|
const int ANNOUNCE_INTERVAL = 1800; // 30min
|
|
|
|
|
|
|
|
// constants
|
|
|
|
const int PEER_ID_SIZE = 20;
|
|
|
|
|
|
|
|
const char ANNOUNCE_REQUEST_PATH[] = "/announce";
|
|
|
|
|
|
|
|
const char ANNOUNCE_REQUEST_COMPACT[] = "compact";
|
|
|
|
const char ANNOUNCE_REQUEST_INFO_HASH[] = "info_hash";
|
|
|
|
const char ANNOUNCE_REQUEST_IP[] = "ip";
|
|
|
|
const char ANNOUNCE_REQUEST_LEFT[] = "left";
|
|
|
|
const char ANNOUNCE_REQUEST_NO_PEER_ID[] = "no_peer_id";
|
|
|
|
const char ANNOUNCE_REQUEST_NUM_WANT[] = "numwant";
|
|
|
|
const char ANNOUNCE_REQUEST_PEER_ID[] = "peer_id";
|
|
|
|
const char ANNOUNCE_REQUEST_PORT[] = "port";
|
|
|
|
|
|
|
|
const char ANNOUNCE_REQUEST_EVENT[] = "event";
|
|
|
|
const char ANNOUNCE_REQUEST_EVENT_COMPLETED[] = "completed";
|
|
|
|
const char ANNOUNCE_REQUEST_EVENT_EMPTY[] = "empty";
|
|
|
|
const char ANNOUNCE_REQUEST_EVENT_STARTED[] = "started";
|
|
|
|
const char ANNOUNCE_REQUEST_EVENT_STOPPED[] = "stopped";
|
|
|
|
const char ANNOUNCE_REQUEST_EVENT_PAUSED[] = "paused";
|
|
|
|
|
|
|
|
const char ANNOUNCE_RESPONSE_COMPLETE[] = "complete";
|
|
|
|
const char ANNOUNCE_RESPONSE_EXTERNAL_IP[] = "external ip";
|
|
|
|
const char ANNOUNCE_RESPONSE_FAILURE_REASON[] = "failure reason";
|
|
|
|
const char ANNOUNCE_RESPONSE_INCOMPLETE[] = "incomplete";
|
|
|
|
const char ANNOUNCE_RESPONSE_INTERVAL[] = "interval";
|
|
|
|
const char ANNOUNCE_RESPONSE_PEERS6[] = "peers6";
|
|
|
|
const char ANNOUNCE_RESPONSE_PEERS[] = "peers";
|
|
|
|
|
|
|
|
const char ANNOUNCE_RESPONSE_PEERS_IP[] = "ip";
|
|
|
|
const char ANNOUNCE_RESPONSE_PEERS_PEER_ID[] = "peer id";
|
|
|
|
const char ANNOUNCE_RESPONSE_PEERS_PORT[] = "port";
|
|
|
|
|
|
|
|
class TrackerError : public RuntimeError
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using RuntimeError::RuntimeError;
|
|
|
|
};
|
|
|
|
|
|
|
|
QByteArray toBigEndianByteArray(const QHostAddress &addr)
|
|
|
|
{
|
|
|
|
// translate IP address to a sequence of bytes in big-endian order
|
|
|
|
switch (addr.protocol()) {
|
|
|
|
case QAbstractSocket::IPv4Protocol:
|
|
|
|
case QAbstractSocket::AnyIPProtocol: {
|
|
|
|
const quint32 ipv4 = addr.toIPv4Address();
|
|
|
|
QByteArray ret;
|
|
|
|
ret.append(static_cast<char>((ipv4 >> 24) & 0xFF))
|
|
|
|
.append(static_cast<char>((ipv4 >> 16) & 0xFF))
|
|
|
|
.append(static_cast<char>((ipv4 >> 8) & 0xFF))
|
|
|
|
.append(static_cast<char>(ipv4 & 0xFF));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
case QAbstractSocket::IPv6Protocol: {
|
|
|
|
const Q_IPV6ADDR ipv6 = addr.toIPv6Address();
|
|
|
|
QByteArray ret;
|
|
|
|
for (int i = (sizeof(ipv6.c) - 1); i >= 0; --i)
|
|
|
|
ret.append(static_cast<char>(ipv6.c[i]));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
case QAbstractSocket::UnknownNetworkLayerProtocol:
|
|
|
|
default:
|
|
|
|
return {};
|
|
|
|
};
|
|
|
|
}
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
namespace BitTorrent
|
2015-01-28 12:03:22 +03:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
// Peer
|
|
|
|
QByteArray Peer::uniqueID() const
|
|
|
|
{
|
|
|
|
return (QByteArray::fromStdString(address) + ':' + QByteArray::number(port));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==(const Peer &left, const Peer &right)
|
|
|
|
{
|
|
|
|
return (left.uniqueID() == right.uniqueID());
|
|
|
|
}
|
2015-01-28 12:03:22 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
bool operator!=(const Peer &left, const Peer &right)
|
|
|
|
{
|
|
|
|
return !(left == right);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint qHash(const Peer &key, const uint seed)
|
|
|
|
{
|
|
|
|
return qHash(key.uniqueID(), seed);
|
|
|
|
}
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
using namespace BitTorrent;
|
|
|
|
|
|
|
|
// TrackerAnnounceRequest
|
|
|
|
struct Tracker::TrackerAnnounceRequest
|
2019-08-08 23:19:53 +08:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
QHostAddress socketAddress;
|
|
|
|
QByteArray claimedAddress; // self claimed by peer
|
|
|
|
InfoHash infoHash;
|
|
|
|
QString event;
|
|
|
|
Peer peer;
|
|
|
|
int numwant = 50;
|
|
|
|
bool compact = true;
|
|
|
|
bool noPeerId = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Tracker::TorrentStats
|
|
|
|
void Tracker::TorrentStats::setPeer(const Peer &peer)
|
|
|
|
{
|
|
|
|
// always replace existing peer
|
|
|
|
if (!removePeer(peer)) {
|
|
|
|
// Too many peers, remove a random one
|
|
|
|
if (peers.size() >= MAX_PEERS_PER_TORRENT)
|
|
|
|
removePeer(*peers.begin());
|
|
|
|
}
|
|
|
|
|
|
|
|
// add peer
|
|
|
|
if (peer.isSeeder)
|
|
|
|
++seeders;
|
|
|
|
peers.insert(peer);
|
2019-08-08 23:19:53 +08:00
|
|
|
}
|
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
bool Tracker::TorrentStats::removePeer(const Peer &peer)
|
2019-08-08 23:19:53 +08:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
const auto iter = peers.find(peer);
|
|
|
|
if (iter == peers.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (iter->isSeeder)
|
|
|
|
--seeders;
|
|
|
|
peers.remove(*iter);
|
|
|
|
return true;
|
2019-08-08 23:19:53 +08:00
|
|
|
}
|
|
|
|
|
2015-04-19 18:17:47 +03:00
|
|
|
// Tracker
|
|
|
|
Tracker::Tracker(QObject *parent)
|
2017-10-14 16:27:21 +03:00
|
|
|
: QObject(parent)
|
2015-01-28 12:03:22 +03:00
|
|
|
, m_server(new Http::Server(this, this))
|
2010-10-15 21:55:56 +00:00
|
|
|
{
|
2015-01-28 12:03:22 +03:00
|
|
|
}
|
|
|
|
|
2015-04-19 18:17:47 +03:00
|
|
|
bool Tracker::start()
|
2010-10-15 21:55:56 +00:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
const QHostAddress ip = QHostAddress::Any;
|
|
|
|
const int port = Preferences::instance()->getTrackerPort();
|
2015-02-05 19:54:15 +03:00
|
|
|
|
|
|
|
if (m_server->isListening()) {
|
2019-08-08 23:21:07 +08:00
|
|
|
if (m_server->serverPort() == port) {
|
2015-02-05 19:54:15 +03:00
|
|
|
// Already listening on the right port, just return
|
|
|
|
return true;
|
|
|
|
}
|
2019-08-08 23:21:07 +08:00
|
|
|
|
2015-02-05 19:54:15 +03:00
|
|
|
// Wrong port, closing the server
|
|
|
|
m_server->close();
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
2015-01-28 12:03:22 +03:00
|
|
|
|
2015-02-05 19:54:15 +03:00
|
|
|
// Listen on the predefined port
|
2019-08-08 23:21:07 +08:00
|
|
|
const bool listenSuccess = m_server->listen(ip, port);
|
|
|
|
|
|
|
|
if (listenSuccess) {
|
|
|
|
LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
|
|
|
|
.arg(ip.toString(), QString::number(port)), Log::INFO);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3")
|
|
|
|
.arg(ip.toString(), QString::number(port), m_server->errorString())
|
|
|
|
, Log::WARNING);
|
|
|
|
}
|
|
|
|
|
|
|
|
return listenSuccess;
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
|
|
|
|
2015-04-19 18:17:47 +03:00
|
|
|
Http::Response Tracker::processRequest(const Http::Request &request, const Http::Environment &env)
|
2010-10-15 21:55:56 +00:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
clear(); // clear response
|
2011-09-25 17:26:02 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
m_request = request;
|
|
|
|
m_env = env;
|
|
|
|
|
|
|
|
status(200);
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Is it a GET request?
|
|
|
|
if (request.method != Http::HEADER_REQUEST_METHOD_GET)
|
|
|
|
throw MethodNotAllowedHTTPError();
|
|
|
|
|
|
|
|
if (request.path.toLower().startsWith(ANNOUNCE_REQUEST_PATH))
|
|
|
|
processAnnounceRequest();
|
|
|
|
else
|
|
|
|
throw NotFoundHTTPError();
|
2015-01-28 12:03:22 +03:00
|
|
|
}
|
2019-08-08 23:21:07 +08:00
|
|
|
catch (const HTTPError &error) {
|
|
|
|
status(error.statusCode(), error.statusText());
|
|
|
|
if (!error.message().isEmpty())
|
|
|
|
print(error.message(), Http::CONTENT_TYPE_TXT);
|
2015-01-28 12:03:22 +03:00
|
|
|
}
|
2019-08-08 23:21:07 +08:00
|
|
|
catch (const TrackerError &error) {
|
|
|
|
clear(); // clear response
|
|
|
|
status(200);
|
|
|
|
|
|
|
|
const lt::entry::dictionary_type bencodedEntry = {
|
|
|
|
{ANNOUNCE_RESPONSE_FAILURE_REASON, {error.what()}}
|
|
|
|
};
|
|
|
|
QByteArray reply;
|
|
|
|
lt::bencode(std::back_inserter(reply), bencodedEntry);
|
|
|
|
print(reply, Http::CONTENT_TYPE_TXT);
|
2015-01-28 12:03:22 +03:00
|
|
|
}
|
2010-10-15 21:55:56 +00:00
|
|
|
|
2015-01-28 12:03:22 +03:00
|
|
|
return response();
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
void Tracker::processAnnounceRequest()
|
2010-10-15 21:55:56 +00:00
|
|
|
{
|
2019-08-04 17:22:28 +08:00
|
|
|
const QHash<QString, QByteArray> &queryParams = m_request.query;
|
2018-12-03 12:06:32 +08:00
|
|
|
TrackerAnnounceRequest announceReq;
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
// ip address
|
|
|
|
announceReq.socketAddress = m_env.clientAddress;
|
|
|
|
announceReq.claimedAddress = queryParams.value(ANNOUNCE_REQUEST_IP);
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
// 1. info_hash
|
|
|
|
const auto infoHashIter = queryParams.find(ANNOUNCE_REQUEST_INFO_HASH);
|
|
|
|
if (infoHashIter == queryParams.end())
|
|
|
|
throw TrackerError("Missing \"info_hash\" parameter");
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
const InfoHash infoHash(infoHashIter->toHex());
|
|
|
|
if (!infoHash.isValid())
|
|
|
|
throw TrackerError("Invalid \"info_hash\" parameter");
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
announceReq.infoHash = infoHash;
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
// 2. peer_id
|
|
|
|
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
|
|
|
|
if (peerIdIter == queryParams.end())
|
|
|
|
throw TrackerError("Missing \"peer_id\" parameter");
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
if (peerIdIter->size() > PEER_ID_SIZE)
|
|
|
|
throw TrackerError("Invalid \"peer_id\" parameter");
|
2015-01-28 12:03:22 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
announceReq.peer.peerId = *peerIdIter;
|
2015-01-28 12:03:22 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
// 3. port
|
|
|
|
const auto portIter = queryParams.find(ANNOUNCE_REQUEST_PORT);
|
|
|
|
if (portIter == queryParams.end())
|
|
|
|
throw TrackerError("Missing \"port\" parameter");
|
2015-01-28 12:03:22 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
const ushort portNum = portIter->toUShort();
|
|
|
|
if (portNum == 0)
|
|
|
|
throw TrackerError("Invalid \"port\" parameter");
|
|
|
|
|
|
|
|
announceReq.peer.port = portNum;
|
|
|
|
|
|
|
|
// 4. numwant
|
|
|
|
const auto numWantIter = queryParams.find(ANNOUNCE_REQUEST_NUM_WANT);
|
|
|
|
if (numWantIter != queryParams.end()) {
|
|
|
|
const int num = numWantIter->toInt();
|
|
|
|
if (num < 0)
|
|
|
|
throw TrackerError("Invalid \"numwant\" parameter");
|
|
|
|
announceReq.numwant = num;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5. no_peer_id
|
|
|
|
// non-formal extension
|
|
|
|
announceReq.noPeerId = (queryParams.value(ANNOUNCE_REQUEST_NO_PEER_ID) == "1");
|
|
|
|
|
|
|
|
// 6. left
|
|
|
|
announceReq.peer.isSeeder = (queryParams.value(ANNOUNCE_REQUEST_LEFT) == "0");
|
|
|
|
|
|
|
|
// 7. compact
|
|
|
|
announceReq.compact = (queryParams.value(ANNOUNCE_REQUEST_COMPACT) != "0");
|
|
|
|
|
|
|
|
// 8. cache `peers` field so we don't recompute when sending response
|
|
|
|
const QHostAddress claimedIPAddress {QString::fromLatin1(announceReq.claimedAddress)};
|
|
|
|
announceReq.peer.endpoint = toBigEndianByteArray(!claimedIPAddress.isNull() ? claimedIPAddress : announceReq.socketAddress)
|
|
|
|
.append(static_cast<char>((announceReq.peer.port >> 8) & 0xFF))
|
|
|
|
.append(static_cast<char>(announceReq.peer.port & 0xFF))
|
|
|
|
.toStdString();
|
|
|
|
|
|
|
|
// 9. cache `address` field so we don't recompute when sending response
|
|
|
|
announceReq.peer.address = !announceReq.claimedAddress.isEmpty()
|
|
|
|
? announceReq.claimedAddress.constData()
|
|
|
|
: announceReq.socketAddress.toString().toLatin1().constData(),
|
|
|
|
|
|
|
|
// 10. event
|
|
|
|
announceReq.event = queryParams.value(ANNOUNCE_REQUEST_EVENT);
|
|
|
|
|
|
|
|
if (announceReq.event.isEmpty()
|
|
|
|
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_EMPTY)
|
|
|
|
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_COMPLETED)
|
|
|
|
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_STARTED)
|
|
|
|
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_PAUSED)) {
|
|
|
|
// [BEP-21] Extension for partial seeds (partial support)
|
|
|
|
registerPeer(announceReq);
|
|
|
|
prepareAnnounceResponse(announceReq);
|
|
|
|
}
|
|
|
|
else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED) {
|
2018-12-03 12:06:32 +08:00
|
|
|
unregisterPeer(announceReq);
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
2015-02-05 19:54:15 +03:00
|
|
|
else {
|
2019-08-08 23:21:07 +08:00
|
|
|
throw TrackerError("Invalid \"event\" parameter");
|
2018-03-08 18:49:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-03 12:06:32 +08:00
|
|
|
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
2018-03-08 18:49:43 +03:00
|
|
|
{
|
2018-12-03 12:06:32 +08:00
|
|
|
if (!m_torrents.contains(announceReq.infoHash)) {
|
2019-08-08 23:21:07 +08:00
|
|
|
// Reached max size, remove a random torrent
|
|
|
|
if (m_torrents.size() >= MAX_TORRENTS)
|
2015-02-05 19:54:15 +03:00
|
|
|
m_torrents.erase(m_torrents.begin());
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
2018-03-08 18:49:43 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
m_torrents[announceReq.infoHash].setPeer(announceReq.peer);
|
2018-03-08 18:49:43 +03:00
|
|
|
}
|
|
|
|
|
2018-12-03 12:06:32 +08:00
|
|
|
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
2018-03-08 18:49:43 +03:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
const auto torrentStatsIter = m_torrents.find(announceReq.infoHash);
|
|
|
|
if (torrentStatsIter == m_torrents.end())
|
|
|
|
return;
|
2015-02-05 19:54:15 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
torrentStatsIter->removePeer(announceReq.peer);
|
|
|
|
|
|
|
|
if (torrentStatsIter->peers.isEmpty())
|
|
|
|
m_torrents.erase(torrentStatsIter);
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
2010-10-15 21:55:56 +00:00
|
|
|
{
|
2019-08-08 23:21:07 +08:00
|
|
|
const TorrentStats &torrentStats = m_torrents[announceReq.infoHash];
|
|
|
|
|
|
|
|
lt::entry::dictionary_type replyDict {
|
|
|
|
{ANNOUNCE_RESPONSE_INTERVAL, ANNOUNCE_INTERVAL},
|
|
|
|
{ANNOUNCE_RESPONSE_COMPLETE, torrentStats.seeders},
|
|
|
|
{ANNOUNCE_RESPONSE_INCOMPLETE, (torrentStats.peers.size() - torrentStats.seeders)},
|
|
|
|
|
|
|
|
// [BEP-24] Tracker Returns External IP
|
|
|
|
{ANNOUNCE_RESPONSE_EXTERNAL_IP, toBigEndianByteArray(announceReq.socketAddress).toStdString()}
|
|
|
|
};
|
|
|
|
|
|
|
|
// peer list
|
|
|
|
// [BEP-7] IPv6 Tracker Extension (partial support)
|
|
|
|
// [BEP-23] Tracker Returns Compact Peer Lists
|
|
|
|
if (announceReq.compact) {
|
|
|
|
lt::entry::list_type peerList;
|
|
|
|
lt::entry::list_type peer6List;
|
|
|
|
|
|
|
|
int counter = 0;
|
|
|
|
for (const Peer &peer : asConst(torrentStats.peers)) {
|
|
|
|
if (counter++ >= announceReq.numwant)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (peer.endpoint.size() == 6) // IPv4
|
|
|
|
peerList.emplace_back(peer.endpoint);
|
|
|
|
else if (peer.endpoint.size() == 18) // IPv6
|
|
|
|
peer6List.emplace_back(peer.endpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
replyDict[ANNOUNCE_RESPONSE_PEERS] = peerList; // required, even it's empty
|
|
|
|
if (!peer6List.empty())
|
|
|
|
replyDict[ANNOUNCE_RESPONSE_PEERS6] = peer6List;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
lt::entry::list_type peerList;
|
|
|
|
|
|
|
|
int counter = 0;
|
|
|
|
for (const Peer &peer : torrentStats.peers) {
|
|
|
|
if (counter++ >= announceReq.numwant)
|
|
|
|
break;
|
|
|
|
|
|
|
|
lt::entry::dictionary_type peerDict = {
|
|
|
|
{ANNOUNCE_RESPONSE_PEERS_IP, peer.address},
|
|
|
|
{ANNOUNCE_RESPONSE_PEERS_PORT, peer.port}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!announceReq.noPeerId)
|
|
|
|
peerDict[ANNOUNCE_RESPONSE_PEERS_PEER_ID] = peer.peerId.constData();
|
2018-03-08 18:49:43 +03:00
|
|
|
|
2019-08-08 23:21:07 +08:00
|
|
|
peerList.emplace_back(peerDict);
|
|
|
|
}
|
|
|
|
|
|
|
|
replyDict[ANNOUNCE_RESPONSE_PEERS] = peerList;
|
|
|
|
}
|
2018-03-08 18:49:43 +03:00
|
|
|
|
2015-02-05 19:54:15 +03:00
|
|
|
// bencode
|
2018-03-08 18:49:43 +03:00
|
|
|
QByteArray reply;
|
2019-08-08 23:21:07 +08:00
|
|
|
lt::bencode(std::back_inserter(reply), replyDict);
|
2015-02-05 19:54:15 +03:00
|
|
|
print(reply, Http::CONTENT_TYPE_TXT);
|
2010-10-15 21:55:56 +00:00
|
|
|
}
|