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 char ANNOUNCE_REQUEST_PATH
[] = "/announce";
58 const char ANNOUNCE_REQUEST_COMPACT
[] = "compact";
59 const char ANNOUNCE_REQUEST_INFO_HASH
[] = "info_hash";
60 const char ANNOUNCE_REQUEST_IP
[] = "ip";
61 const char ANNOUNCE_REQUEST_LEFT
[] = "left";
62 const char ANNOUNCE_REQUEST_NO_PEER_ID
[] = "no_peer_id";
63 const char ANNOUNCE_REQUEST_NUM_WANT
[] = "numwant";
64 const char ANNOUNCE_REQUEST_PEER_ID
[] = "peer_id";
65 const char ANNOUNCE_REQUEST_PORT
[] = "port";
67 const char ANNOUNCE_REQUEST_EVENT
[] = "event";
68 const char ANNOUNCE_REQUEST_EVENT_COMPLETED
[] = "completed";
69 const char ANNOUNCE_REQUEST_EVENT_EMPTY
[] = "empty";
70 const char ANNOUNCE_REQUEST_EVENT_STARTED
[] = "started";
71 const char ANNOUNCE_REQUEST_EVENT_STOPPED
[] = "stopped";
72 const char ANNOUNCE_REQUEST_EVENT_PAUSED
[] = "paused";
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 bool operator!=(const Peer
&left
, const Peer
&right
)
140 return !(left
== right
);
143 uint
qHash(const Peer
&key
, const uint seed
)
145 return qHash(key
.uniqueID(), seed
);
149 using namespace BitTorrent
;
151 // TrackerAnnounceRequest
152 struct Tracker::TrackerAnnounceRequest
154 QHostAddress socketAddress
;
155 QByteArray claimedAddress
; // self claimed by peer
161 bool noPeerId
= false;
164 // Tracker::TorrentStats
165 void Tracker::TorrentStats::setPeer(const Peer
&peer
)
167 // always replace existing peer
168 if (!removePeer(peer
))
170 // Too many peers, remove a random one
171 if (peers
.size() >= MAX_PEERS_PER_TORRENT
)
172 removePeer(*peers
.begin());
181 bool Tracker::TorrentStats::removePeer(const Peer
&peer
)
183 const auto iter
= peers
.find(peer
);
184 if (iter
== peers
.end())
194 Tracker::Tracker(QObject
*parent
)
196 , m_server(new Http::Server(this, this))
200 bool Tracker::start()
202 const QHostAddress ip
= QHostAddress::Any
;
203 const int port
= Preferences::instance()->getTrackerPort();
205 if (m_server
->isListening())
207 if (m_server
->serverPort() == port
)
209 // Already listening on the right port, just return
213 // Wrong port, closing the server
217 // Listen on the predefined port
218 const bool listenSuccess
= m_server
->listen(ip
, port
);
222 LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
223 .arg(ip
.toString(), QString::number(port
)), Log::INFO
);
227 LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3")
228 .arg(ip
.toString(), QString::number(port
), m_server
->errorString())
232 return listenSuccess
;
235 Http::Response
Tracker::processRequest(const Http::Request
&request
, const Http::Environment
&env
)
237 clear(); // clear response
246 // Is it a GET request?
247 if (request
.method
!= Http::HEADER_REQUEST_METHOD_GET
)
248 throw MethodNotAllowedHTTPError();
250 if (request
.path
.startsWith(ANNOUNCE_REQUEST_PATH
, Qt::CaseInsensitive
))
251 processAnnounceRequest();
253 throw NotFoundHTTPError();
255 catch (const HTTPError
&error
)
257 status(error
.statusCode(), error
.statusText());
258 if (!error
.message().isEmpty())
259 print(error
.message(), Http::CONTENT_TYPE_TXT
);
261 catch (const TrackerError
&error
)
263 clear(); // clear response
266 const lt::entry::dictionary_type bencodedEntry
=
268 {ANNOUNCE_RESPONSE_FAILURE_REASON
, {error
.message().toStdString()}}
271 lt::bencode(std::back_inserter(reply
), bencodedEntry
);
272 print(reply
, Http::CONTENT_TYPE_TXT
);
278 void Tracker::processAnnounceRequest()
280 const QHash
<QString
, QByteArray
> &queryParams
= m_request
.query
;
281 TrackerAnnounceRequest announceReq
;
284 announceReq
.socketAddress
= m_env
.clientAddress
;
285 announceReq
.claimedAddress
= queryParams
.value(ANNOUNCE_REQUEST_IP
);
287 // Enforce using IPv4 if address is indeed IPv4 or if it is an IPv4-mapped IPv6 address
289 const qint32 decimalIPv4
= announceReq
.socketAddress
.toIPv4Address(&ok
);
291 announceReq
.socketAddress
= QHostAddress(decimalIPv4
);
294 const auto infoHashIter
= queryParams
.find(ANNOUNCE_REQUEST_INFO_HASH
);
295 if (infoHashIter
== queryParams
.end())
296 throw TrackerError("Missing \"info_hash\" parameter");
298 const auto torrentID
= TorrentID::fromString(infoHashIter
->toHex());
299 if (!torrentID
.isValid())
300 throw TrackerError("Invalid \"info_hash\" parameter");
302 announceReq
.torrentID
= torrentID
;
305 const auto peerIdIter
= queryParams
.find(ANNOUNCE_REQUEST_PEER_ID
);
306 if (peerIdIter
== queryParams
.end())
307 throw TrackerError("Missing \"peer_id\" parameter");
309 if (peerIdIter
->size() > PEER_ID_SIZE
)
310 throw TrackerError("Invalid \"peer_id\" parameter");
312 announceReq
.peer
.peerId
= *peerIdIter
;
315 const auto portIter
= queryParams
.find(ANNOUNCE_REQUEST_PORT
);
316 if (portIter
== queryParams
.end())
317 throw TrackerError("Missing \"port\" parameter");
319 const ushort portNum
= portIter
->toUShort();
321 throw TrackerError("Invalid \"port\" parameter");
323 announceReq
.peer
.port
= portNum
;
326 const auto numWantIter
= queryParams
.find(ANNOUNCE_REQUEST_NUM_WANT
);
327 if (numWantIter
!= queryParams
.end())
329 const int num
= numWantIter
->toInt();
331 throw TrackerError("Invalid \"numwant\" parameter");
332 announceReq
.numwant
= num
;
336 // non-formal extension
337 announceReq
.noPeerId
= (queryParams
.value(ANNOUNCE_REQUEST_NO_PEER_ID
) == "1");
340 announceReq
.peer
.isSeeder
= (queryParams
.value(ANNOUNCE_REQUEST_LEFT
) == "0");
343 announceReq
.compact
= (queryParams
.value(ANNOUNCE_REQUEST_COMPACT
) != "0");
345 // 8. cache `peers` field so we don't recompute when sending response
346 const QHostAddress claimedIPAddress
{QString::fromLatin1(announceReq
.claimedAddress
)};
347 announceReq
.peer
.endpoint
= toBigEndianByteArray(!claimedIPAddress
.isNull() ? claimedIPAddress
: announceReq
.socketAddress
)
348 .append(static_cast<char>((announceReq
.peer
.port
>> 8) & 0xFF))
349 .append(static_cast<char>(announceReq
.peer
.port
& 0xFF))
352 // 9. cache `address` field so we don't recompute when sending response
353 announceReq
.peer
.address
= !announceReq
.claimedAddress
.isEmpty()
354 ? announceReq
.claimedAddress
.constData()
355 : announceReq
.socketAddress
.toString().toLatin1().constData(),
358 announceReq
.event
= queryParams
.value(ANNOUNCE_REQUEST_EVENT
);
360 if (announceReq
.event
.isEmpty()
361 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_EMPTY
)
362 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_COMPLETED
)
363 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_STARTED
)
364 || (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_PAUSED
))
366 // [BEP-21] Extension for partial seeds
367 // (partial support - we don't support BEP-48 so the part that concerns that is not supported)
368 registerPeer(announceReq
);
370 else if (announceReq
.event
== ANNOUNCE_REQUEST_EVENT_STOPPED
)
372 unregisterPeer(announceReq
);
376 throw TrackerError("Invalid \"event\" parameter");
379 prepareAnnounceResponse(announceReq
);
382 void Tracker::registerPeer(const TrackerAnnounceRequest
&announceReq
)
384 if (!m_torrents
.contains(announceReq
.torrentID
))
386 // Reached max size, remove a random torrent
387 if (m_torrents
.size() >= MAX_TORRENTS
)
388 m_torrents
.erase(m_torrents
.begin());
391 m_torrents
[announceReq
.torrentID
].setPeer(announceReq
.peer
);
394 void Tracker::unregisterPeer(const TrackerAnnounceRequest
&announceReq
)
396 const auto torrentStatsIter
= m_torrents
.find(announceReq
.torrentID
);
397 if (torrentStatsIter
== m_torrents
.end())
400 torrentStatsIter
->removePeer(announceReq
.peer
);
402 if (torrentStatsIter
->peers
.isEmpty())
403 m_torrents
.erase(torrentStatsIter
);
406 void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest
&announceReq
)
408 const TorrentStats
&torrentStats
= m_torrents
[announceReq
.torrentID
];
410 lt::entry::dictionary_type replyDict
412 {ANNOUNCE_RESPONSE_INTERVAL
, ANNOUNCE_INTERVAL
},
413 {ANNOUNCE_RESPONSE_COMPLETE
, torrentStats
.seeders
},
414 {ANNOUNCE_RESPONSE_INCOMPLETE
, (torrentStats
.peers
.size() - torrentStats
.seeders
)},
416 // [BEP-24] Tracker Returns External IP (partial support - might not work properly for all IPv6 cases)
417 {ANNOUNCE_RESPONSE_EXTERNAL_IP
, toBigEndianByteArray(announceReq
.socketAddress
).toStdString()}
421 // [BEP-7] IPv6 Tracker Extension (partial support - only the part that concerns BEP-23)
422 // [BEP-23] Tracker Returns Compact Peer Lists
423 if (announceReq
.compact
)
425 lt::entry::string_type peers
;
426 lt::entry::string_type peers6
;
428 if (announceReq
.event
!= ANNOUNCE_REQUEST_EVENT_STOPPED
)
431 for (const Peer
&peer
: asConst(torrentStats
.peers
))
433 if (counter
++ >= announceReq
.numwant
)
436 if (peer
.endpoint
.size() == 6) // IPv4 + port
437 peers
.append(peer
.endpoint
);
438 else if (peer
.endpoint
.size() == 18) // IPv6 + port
439 peers6
.append(peer
.endpoint
);
443 replyDict
[ANNOUNCE_RESPONSE_PEERS
] = peers
; // required, even it's empty
445 replyDict
[ANNOUNCE_RESPONSE_PEERS6
] = peers6
;
449 lt::entry::list_type peerList
;
451 if (announceReq
.event
!= ANNOUNCE_REQUEST_EVENT_STOPPED
)
454 for (const Peer
&peer
: torrentStats
.peers
)
456 if (counter
++ >= announceReq
.numwant
)
459 lt::entry::dictionary_type peerDict
=
461 {ANNOUNCE_RESPONSE_PEERS_IP
, peer
.address
},
462 {ANNOUNCE_RESPONSE_PEERS_PORT
, peer
.port
}
465 if (!announceReq
.noPeerId
)
466 peerDict
[ANNOUNCE_RESPONSE_PEERS_PEER_ID
] = peer
.peerId
.constData();
468 peerList
.emplace_back(peerDict
);
472 replyDict
[ANNOUNCE_RESPONSE_PEERS
] = peerList
;
477 lt::bencode(std::back_inserter(reply
), replyDict
);
478 print(reply
, Http::CONTENT_TYPE_TXT
);