2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2019 Mike Tzou (Chocobo1)
4 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
5 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
33 #include <libtorrent/bencode.hpp>
34 #include <libtorrent/entry.hpp>
36 #include <QHostAddress>
38 #include "base/exceptions.h"
39 #include "base/global.h"
40 #include "base/http/httperror.h"
41 #include "base/http/server.h"
42 #include "base/http/types.h"
43 #include "base/logger.h"
44 #include "base/preferences.h"
49 const int MAX_TORRENTS
= 10000;
50 const int MAX_PEERS_PER_TORRENT
= 200;
51 const int ANNOUNCE_INTERVAL
= 1800; // 30min
54 const int PEER_ID_SIZE
= 20;
56 const QString ANNOUNCE_REQUEST_PATH
= u
"/announce"_s
;
58 const QString ANNOUNCE_REQUEST_COMPACT
= u
"compact"_s
;
59 const QString ANNOUNCE_REQUEST_INFO_HASH
= u
"info_hash"_s
;
60 const QString ANNOUNCE_REQUEST_IP
= u
"ip"_s
;
61 const QString ANNOUNCE_REQUEST_LEFT
= u
"left"_s
;
62 const QString ANNOUNCE_REQUEST_NO_PEER_ID
= u
"no_peer_id"_s
;
63 const QString ANNOUNCE_REQUEST_NUM_WANT
= u
"numwant"_s
;
64 const QString ANNOUNCE_REQUEST_PEER_ID
= u
"peer_id"_s
;
65 const QString ANNOUNCE_REQUEST_PORT
= u
"port"_s
;
67 const QString ANNOUNCE_REQUEST_EVENT
= u
"event"_s
;
68 const QString ANNOUNCE_REQUEST_EVENT_COMPLETED
= u
"completed"_s
;
69 const QString ANNOUNCE_REQUEST_EVENT_EMPTY
= u
"empty"_s
;
70 const QString ANNOUNCE_REQUEST_EVENT_STARTED
= u
"started"_s
;
71 const QString ANNOUNCE_REQUEST_EVENT_STOPPED
= u
"stopped"_s
;
72 const QString ANNOUNCE_REQUEST_EVENT_PAUSED
= u
"paused"_s
;
74 const char ANNOUNCE_RESPONSE_COMPLETE
[] = "complete";
75 const char ANNOUNCE_RESPONSE_EXTERNAL_IP
[] = "external ip";
76 const char ANNOUNCE_RESPONSE_FAILURE_REASON
[] = "failure reason";
77 const char ANNOUNCE_RESPONSE_INCOMPLETE
[] = "incomplete";
78 const char ANNOUNCE_RESPONSE_INTERVAL
[] = "interval";
79 const char ANNOUNCE_RESPONSE_PEERS6
[] = "peers6";
80 const char ANNOUNCE_RESPONSE_PEERS
[] = "peers";
82 const char ANNOUNCE_RESPONSE_PEERS_IP
[] = "ip";
83 const char ANNOUNCE_RESPONSE_PEERS_PEER_ID
[] = "peer id";
84 const char ANNOUNCE_RESPONSE_PEERS_PORT
[] = "port";
86 class TrackerError
: public RuntimeError
89 using RuntimeError::RuntimeError
;
92 QByteArray
toBigEndianByteArray(const QHostAddress
&addr
)
94 // translate IP address to a sequence of bytes in big-endian order
95 switch (addr
.protocol())
97 case QAbstractSocket::IPv4Protocol
:
98 case QAbstractSocket::AnyIPProtocol
:
100 const quint32 ipv4
= addr
.toIPv4Address();
102 ret
.append(static_cast<char>((ipv4
>> 24) & 0xFF))
103 .append(static_cast<char>((ipv4
>> 16) & 0xFF))
104 .append(static_cast<char>((ipv4
>> 8) & 0xFF))
105 .append(static_cast<char>(ipv4
& 0xFF));
109 case QAbstractSocket::IPv6Protocol
:
111 const Q_IPV6ADDR ipv6
= addr
.toIPv6Address();
113 for (const quint8 i
: ipv6
.c
)
118 case QAbstractSocket::UnknownNetworkLayerProtocol
:
128 QByteArray
Peer::uniqueID() const
130 return (QByteArray::fromStdString(address
) + ':' + QByteArray::number(port
));
133 bool operator==(const Peer
&left
, const Peer
&right
)
135 return (left
.uniqueID() == right
.uniqueID());
138 std::size_t qHash(const Peer
&key
, const std::size_t seed
)
140 return qHash(key
.uniqueID(), seed
);
144 using namespace BitTorrent
;
146 // TrackerAnnounceRequest
147 struct Tracker::TrackerAnnounceRequest
149 QHostAddress socketAddress
;
150 QByteArray claimedAddress
; // self claimed by peer
156 bool noPeerId
= false;
159 // Tracker::TorrentStats
160 void Tracker::TorrentStats::setPeer(const Peer
&peer
)
162 // always replace existing peer
163 if (!removePeer(peer
))
165 // Too many peers, remove a random one
166 if (peers
.size() >= MAX_PEERS_PER_TORRENT
)
167 removePeer(*peers
.begin());
176 bool Tracker::TorrentStats::removePeer(const Peer
&peer
)
178 const auto iter
= peers
.find(peer
);
179 if (iter
== peers
.end())
189 Tracker::Tracker(QObject
*parent
)
191 , m_server(new Http::Server(this, this))
195 bool Tracker::start()
197 const int port
= Preferences::instance()->getTrackerPort();
199 if (m_server
->isListening())
201 if (const int oldPort
= m_server
->serverPort()
204 // Already listening on the right port, just return
208 // Wrong port, closing the server
213 const QHostAddress ip
= QHostAddress::Any
;
214 const bool listenSuccess
= m_server
->listen(ip
, port
);
217 LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
218 .arg(ip
.toString(), QString::number(port
)), Log::INFO
);
222 LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3")
223 .arg(ip
.toString(), QString::number(port
), m_server
->errorString())
227 return listenSuccess
;
230 Http::Response
Tracker::processRequest(const Http::Request
&request
, const Http::Environment
&env
)
232 clear(); // clear response
241 // Is it a GET request?
242 if (request
.method
!= Http::HEADER_REQUEST_METHOD_GET
)
243 throw MethodNotAllowedHTTPError();
245 if (request
.path
.startsWith(ANNOUNCE_REQUEST_PATH
, Qt::CaseInsensitive
))
246 processAnnounceRequest();
248 throw NotFoundHTTPError();
250 catch (const HTTPError
&error
)
252 status(error
.statusCode(), error
.statusText());
253 if (!error
.message().isEmpty())
254 print(error
.message(), Http::CONTENT_TYPE_TXT
);
256 catch (const TrackerError
&error
)
258 clear(); // clear response
261 const lt::entry::dictionary_type bencodedEntry
=
263 {ANNOUNCE_RESPONSE_FAILURE_REASON
, {error
.message().toStdString()}}
266 lt::bencode(std::back_inserter(reply
), bencodedEntry
);
267 print(reply
, Http::CONTENT_TYPE_TXT
);
273 void Tracker::processAnnounceRequest()
275 const QHash
<QString
, QByteArray
> &queryParams
= m_request
.query
;
276 TrackerAnnounceRequest announceReq
;
279 announceReq
.socketAddress
= m_env
.clientAddress
;
280 announceReq
.claimedAddress
= queryParams
.value(ANNOUNCE_REQUEST_IP
);
282 // Enforce using IPv4 if address is indeed IPv4 or if it is an IPv4-mapped IPv6 address
284 const qint32 decimalIPv4
= announceReq
.socketAddress
.toIPv4Address(&ok
);
286 announceReq
.socketAddress
= QHostAddress(decimalIPv4
);
289 const auto infoHashIter
= queryParams
.find(ANNOUNCE_REQUEST_INFO_HASH
);
290 if (infoHashIter
== queryParams
.end())
291 throw TrackerError(u
"Missing \"info_hash\" parameter"_s
);
293 const auto torrentID
= TorrentID::fromString(QString::fromLatin1(infoHashIter
->toHex()));
294 if (!torrentID
.isValid())
295 throw TrackerError(u
"Invalid \"info_hash\" parameter"_s
);
297 announceReq
.torrentID
= torrentID
;
300 const auto peerIdIter
= queryParams
.find(ANNOUNCE_REQUEST_PEER_ID
);
301 if (peerIdIter
== queryParams
.end())
302 throw TrackerError(u
"Missing \"peer_id\" parameter"_s
);
304 if (peerIdIter
->size() > PEER_ID_SIZE
)
305 throw TrackerError(u
"Invalid \"peer_id\" parameter"_s
);
307 announceReq
.peer
.peerId
= *peerIdIter
;
310 const auto portIter
= queryParams
.find(ANNOUNCE_REQUEST_PORT
);
311 if (portIter
== queryParams
.end())
312 throw TrackerError(u
"Missing \"port\" parameter"_s
);
314 const ushort portNum
= portIter
->toUShort();
316 throw TrackerError(u
"Invalid \"port\" parameter"_s
);
318 announceReq
.peer
.port
= portNum
;
321 const auto numWantIter
= queryParams
.find(ANNOUNCE_REQUEST_NUM_WANT
);
322 if (numWantIter
!= queryParams
.end())
324 const int num
= numWantIter
->toInt();
326 throw TrackerError(u
"Invalid \"numwant\" parameter"_s
);
327 announceReq
.numwant
= num
;
331 // non-formal extension
332 announceReq
.noPeerId
= (queryParams
.value(ANNOUNCE_REQUEST_NO_PEER_ID
) == "1");
335 announceReq
.peer
.isSeeder
= (queryParams
.value(ANNOUNCE_REQUEST_LEFT
) == "0");
338 announceReq
.compact
= (queryParams
.value(ANNOUNCE_REQUEST_COMPACT
) != "0");
340 // 8. cache `peers` field so we don't recompute when sending response
341 const QHostAddress claimedIPAddress
{QString::fromLatin1(announceReq
.claimedAddress
)};
342 announceReq
.peer
.endpoint
= toBigEndianByteArray(!claimedIPAddress
.isNull() ? claimedIPAddress
: announceReq
.socketAddress
)
343 .append(static_cast<char>((announceReq
.peer
.port
>> 8) & 0xFF))
344 .append(static_cast<char>(announceReq
.peer
.port
& 0xFF))
347 // 9. cache `address` field so we don't recompute when sending response
348 announceReq
.peer
.address
= !announceReq
.claimedAddress
.isEmpty()
349 ? announceReq
.claimedAddress
.constData()
350 : announceReq
.socketAddress
.toString().toLatin1().constData(),
353 announceReq
.event
= QString::fromLatin1(queryParams
.value(ANNOUNCE_REQUEST_EVENT
));
355 if (announceReq
.event
.isEmpty()
356 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_EMPTY
)
357 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_COMPLETED
)
358 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_STARTED
)
359 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_PAUSED
))
361 // [BEP-21] Extension for partial seeds
362 // (partial support - we don't support BEP-48 so the part that concerns that is not supported)
363 registerPeer(announceReq
);
365 else if (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_STOPPED
)
367 unregisterPeer(announceReq
);
371 throw TrackerError(u
"Invalid \"event\" parameter"_s
);
374 prepareAnnounceResponse(announceReq
);
377 void Tracker::registerPeer(const TrackerAnnounceRequest
&announceReq
)
379 if (!m_torrents
.contains(announceReq
.torrentID
))
381 // Reached max size, remove a random torrent
382 if (m_torrents
.size() >= MAX_TORRENTS
)
383 m_torrents
.erase(m_torrents
.begin());
386 m_torrents
[announceReq
.torrentID
].setPeer(announceReq
.peer
);
389 void Tracker::unregisterPeer(const TrackerAnnounceRequest
&announceReq
)
391 const auto torrentStatsIter
= m_torrents
.find(announceReq
.torrentID
);
392 if (torrentStatsIter
== m_torrents
.end())
395 torrentStatsIter
->removePeer(announceReq
.peer
);
397 if (torrentStatsIter
->peers
.isEmpty())
398 m_torrents
.erase(torrentStatsIter
);
401 void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest
&announceReq
)
403 const TorrentStats
&torrentStats
= m_torrents
[announceReq
.torrentID
];
405 lt::entry::dictionary_type replyDict
407 {ANNOUNCE_RESPONSE_INTERVAL
, ANNOUNCE_INTERVAL
},
408 {ANNOUNCE_RESPONSE_COMPLETE
, torrentStats
.seeders
},
409 {ANNOUNCE_RESPONSE_INCOMPLETE
, (torrentStats
.peers
.size() - torrentStats
.seeders
)},
411 // [BEP-24] Tracker Returns External IP (partial support - might not work properly for all IPv6 cases)
412 {ANNOUNCE_RESPONSE_EXTERNAL_IP
, toBigEndianByteArray(announceReq
.socketAddress
).toStdString()}
416 // [BEP-7] IPv6 Tracker Extension (partial support - only the part that concerns BEP-23)
417 // [BEP-23] Tracker Returns Compact Peer Lists
418 if (announceReq
.compact
)
420 lt::entry::string_type peers
;
421 lt::entry::string_type peers6
;
423 if (announceReq
.event
!= ANNOUNCE_REQUEST_EVENT_STOPPED
)
426 for (const Peer
&peer
: asConst(torrentStats
.peers
))
428 if (counter
++ >= announceReq
.numwant
)
431 if (peer
.endpoint
.size() == 6) // IPv4 + port
432 peers
.append(peer
.endpoint
);
433 else if (peer
.endpoint
.size() == 18) // IPv6 + port
434 peers6
.append(peer
.endpoint
);
438 replyDict
[ANNOUNCE_RESPONSE_PEERS
] = peers
; // required, even it's empty
440 replyDict
[ANNOUNCE_RESPONSE_PEERS6
] = peers6
;
444 lt::entry::list_type peerList
;
446 if (announceReq
.event
!= ANNOUNCE_REQUEST_EVENT_STOPPED
)
449 for (const Peer
&peer
: torrentStats
.peers
)
451 if (counter
++ >= announceReq
.numwant
)
454 lt::entry::dictionary_type peerDict
=
456 {ANNOUNCE_RESPONSE_PEERS_IP
, peer
.address
},
457 {ANNOUNCE_RESPONSE_PEERS_PORT
, peer
.port
}
460 if (!announceReq
.noPeerId
)
461 peerDict
[ANNOUNCE_RESPONSE_PEERS_PEER_ID
] = peer
.peerId
.constData();
463 peerList
.emplace_back(peerDict
);
467 replyDict
[ANNOUNCE_RESPONSE_PEERS
] = peerList
;
472 lt::bencode(std::back_inserter(reply
), replyDict
);
473 print(reply
, Http::CONTENT_TYPE_TXT
);