1 /*
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>
6 *
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.
11 *
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.
16 *
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.
20 *
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.
29 */
30
31 #include "tracker.h"
32
33 #include <libtorrent/bencode.hpp>
34 #include <libtorrent/entry.hpp>
35
36 #include <QHostAddress>
37
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"
45
46 namespace
47 {
48 // static limits
49 const int MAX_TORRENTS = 10000;
50 const int MAX_PEERS_PER_TORRENT = 200;
51 const int ANNOUNCE_INTERVAL = 1800; // 30min
52
53 // constants
54 const int PEER_ID_SIZE = 20;
55
56 const char ANNOUNCE_REQUEST_PATH[] = "/announce";
57
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";
66
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";
73
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";
81
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";
85
86 class TrackerError : public RuntimeError
87 {
88 public:
89 using RuntimeError::RuntimeError;
90 };
91
toBigEndianByteArray(const QHostAddress & addr)92 QByteArray toBigEndianByteArray(const QHostAddress &addr)
93 {
94 // translate IP address to a sequence of bytes in big-endian order
95 switch (addr.protocol())
96 {
97 case QAbstractSocket::IPv4Protocol:
98 case QAbstractSocket::AnyIPProtocol:
99 {
100 const quint32 ipv4 = addr.toIPv4Address();
101 QByteArray ret;
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));
106 return ret;
107 }
108
109 case QAbstractSocket::IPv6Protocol:
110 {
111 const Q_IPV6ADDR ipv6 = addr.toIPv6Address();
112 QByteArray ret;
113 for (const quint8 i : ipv6.c)
114 ret.append(i);
115 return ret;
116 }
117
118 case QAbstractSocket::UnknownNetworkLayerProtocol:
119 default:
120 return {};
121 };
122 }
123 }
124
125 namespace BitTorrent
126 {
127 // Peer
uniqueID() const128 QByteArray Peer::uniqueID() const
129 {
130 return (QByteArray::fromStdString(address) + ':' + QByteArray::number(port));
131 }
132
operator ==(const Peer & left,const Peer & right)133 bool operator==(const Peer &left, const Peer &right)
134 {
135 return (left.uniqueID() == right.uniqueID());
136 }
137
operator !=(const Peer & left,const Peer & right)138 bool operator!=(const Peer &left, const Peer &right)
139 {
140 return !(left == right);
141 }
142
qHash(const Peer & key,const uint seed)143 uint qHash(const Peer &key, const uint seed)
144 {
145 return qHash(key.uniqueID(), seed);
146 }
147 }
148
149 using namespace BitTorrent;
150
151 // TrackerAnnounceRequest
152 struct Tracker::TrackerAnnounceRequest
153 {
154 QHostAddress socketAddress;
155 QByteArray claimedAddress; // self claimed by peer
156 TorrentID torrentID;
157 QString event;
158 Peer peer;
159 int numwant = 50;
160 bool compact = true;
161 bool noPeerId = false;
162 };
163
164 // Tracker::TorrentStats
setPeer(const Peer & peer)165 void Tracker::TorrentStats::setPeer(const Peer &peer)
166 {
167 // always replace existing peer
168 if (!removePeer(peer))
169 {
170 // Too many peers, remove a random one
171 if (peers.size() >= MAX_PEERS_PER_TORRENT)
172 removePeer(*peers.begin());
173 }
174
175 // add peer
176 if (peer.isSeeder)
177 ++seeders;
178 peers.insert(peer);
179 }
180
removePeer(const Peer & peer)181 bool Tracker::TorrentStats::removePeer(const Peer &peer)
182 {
183 const auto iter = peers.find(peer);
184 if (iter == peers.end())
185 return false;
186
187 if (iter->isSeeder)
188 --seeders;
189 peers.remove(*iter);
190 return true;
191 }
192
193 // Tracker
Tracker(QObject * parent)194 Tracker::Tracker(QObject *parent)
195 : QObject(parent)
196 , m_server(new Http::Server(this, this))
197 {
198 }
199
start()200 bool Tracker::start()
201 {
202 const QHostAddress ip = QHostAddress::Any;
203 const int port = Preferences::instance()->getTrackerPort();
204
205 if (m_server->isListening())
206 {
207 if (m_server->serverPort() == port)
208 {
209 // Already listening on the right port, just return
210 return true;
211 }
212
213 // Wrong port, closing the server
214 m_server->close();
215 }
216
217 // Listen on the predefined port
218 const bool listenSuccess = m_server->listen(ip, port);
219
220 if (listenSuccess)
221 {
222 LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
223 .arg(ip.toString(), QString::number(port)), Log::INFO);
224 }
225 else
226 {
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())
229 , Log::WARNING);
230 }
231
232 return listenSuccess;
233 }
234
processRequest(const Http::Request & request,const Http::Environment & env)235 Http::Response Tracker::processRequest(const Http::Request &request, const Http::Environment &env)
236 {
237 clear(); // clear response
238
239 m_request = request;
240 m_env = env;
241
242 status(200);
243
244 try
245 {
246 // Is it a GET request?
247 if (request.method != Http::HEADER_REQUEST_METHOD_GET)
248 throw MethodNotAllowedHTTPError();
249
250 if (request.path.startsWith(ANNOUNCE_REQUEST_PATH, Qt::CaseInsensitive))
251 processAnnounceRequest();
252 else
253 throw NotFoundHTTPError();
254 }
255 catch (const HTTPError &error)
256 {
257 status(error.statusCode(), error.statusText());
258 if (!error.message().isEmpty())
259 print(error.message(), Http::CONTENT_TYPE_TXT);
260 }
261 catch (const TrackerError &error)
262 {
263 clear(); // clear response
264 status(200);
265
266 const lt::entry::dictionary_type bencodedEntry =
267 {
268 {ANNOUNCE_RESPONSE_FAILURE_REASON, {error.message().toStdString()}}
269 };
270 QByteArray reply;
271 lt::bencode(std::back_inserter(reply), bencodedEntry);
272 print(reply, Http::CONTENT_TYPE_TXT);
273 }
274
275 return response();
276 }
277
processAnnounceRequest()278 void Tracker::processAnnounceRequest()
279 {
280 const QHash<QString, QByteArray> &queryParams = m_request.query;
281 TrackerAnnounceRequest announceReq;
282
283 // ip address
284 announceReq.socketAddress = m_env.clientAddress;
285 announceReq.claimedAddress = queryParams.value(ANNOUNCE_REQUEST_IP);
286
287 // Enforce using IPv4 if address is indeed IPv4 or if it is an IPv4-mapped IPv6 address
288 bool ok = false;
289 const qint32 decimalIPv4 = announceReq.socketAddress.toIPv4Address(&ok);
290 if (ok)
291 announceReq.socketAddress = QHostAddress(decimalIPv4);
292
293 // 1. info_hash
294 const auto infoHashIter = queryParams.find(ANNOUNCE_REQUEST_INFO_HASH);
295 if (infoHashIter == queryParams.end())
296 throw TrackerError("Missing \"info_hash\" parameter");
297
298 const auto torrentID = TorrentID::fromString(infoHashIter->toHex());
299 if (!torrentID.isValid())
300 throw TrackerError("Invalid \"info_hash\" parameter");
301
302 announceReq.torrentID = torrentID;
303
304 // 2. peer_id
305 const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
306 if (peerIdIter == queryParams.end())
307 throw TrackerError("Missing \"peer_id\" parameter");
308
309 if (peerIdIter->size() > PEER_ID_SIZE)
310 throw TrackerError("Invalid \"peer_id\" parameter");
311
312 announceReq.peer.peerId = *peerIdIter;
313
314 // 3. port
315 const auto portIter = queryParams.find(ANNOUNCE_REQUEST_PORT);
316 if (portIter == queryParams.end())
317 throw TrackerError("Missing \"port\" parameter");
318
319 const ushort portNum = portIter->toUShort();
320 if (portNum == 0)
321 throw TrackerError("Invalid \"port\" parameter");
322
323 announceReq.peer.port = portNum;
324
325 // 4. numwant
326 const auto numWantIter = queryParams.find(ANNOUNCE_REQUEST_NUM_WANT);
327 if (numWantIter != queryParams.end())
328 {
329 const int num = numWantIter->toInt();
330 if (num < 0)
331 throw TrackerError("Invalid \"numwant\" parameter");
332 announceReq.numwant = num;
333 }
334
335 // 5. no_peer_id
336 // non-formal extension
337 announceReq.noPeerId = (queryParams.value(ANNOUNCE_REQUEST_NO_PEER_ID) == "1");
338
339 // 6. left
340 announceReq.peer.isSeeder = (queryParams.value(ANNOUNCE_REQUEST_LEFT) == "0");
341
342 // 7. compact
343 announceReq.compact = (queryParams.value(ANNOUNCE_REQUEST_COMPACT) != "0");
344
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))
350 .toStdString();
351
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(),
356
357 // 10. event
358 announceReq.event = queryParams.value(ANNOUNCE_REQUEST_EVENT);
359
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))
365 {
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);
369 }
370 else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED)
371 {
372 unregisterPeer(announceReq);
373 }
374 else
375 {
376 throw TrackerError("Invalid \"event\" parameter");
377 }
378
379 prepareAnnounceResponse(announceReq);
380 }
381
registerPeer(const TrackerAnnounceRequest & announceReq)382 void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
383 {
384 if (!m_torrents.contains(announceReq.torrentID))
385 {
386 // Reached max size, remove a random torrent
387 if (m_torrents.size() >= MAX_TORRENTS)
388 m_torrents.erase(m_torrents.begin());
389 }
390
391 m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
392 }
393
unregisterPeer(const TrackerAnnounceRequest & announceReq)394 void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
395 {
396 const auto torrentStatsIter = m_torrents.find(announceReq.torrentID);
397 if (torrentStatsIter == m_torrents.end())
398 return;
399
400 torrentStatsIter->removePeer(announceReq.peer);
401
402 if (torrentStatsIter->peers.isEmpty())
403 m_torrents.erase(torrentStatsIter);
404 }
405
prepareAnnounceResponse(const TrackerAnnounceRequest & announceReq)406 void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
407 {
408 const TorrentStats &torrentStats = m_torrents[announceReq.torrentID];
409
410 lt::entry::dictionary_type replyDict
411 {
412 {ANNOUNCE_RESPONSE_INTERVAL, ANNOUNCE_INTERVAL},
413 {ANNOUNCE_RESPONSE_COMPLETE, torrentStats.seeders},
414 {ANNOUNCE_RESPONSE_INCOMPLETE, (torrentStats.peers.size() - torrentStats.seeders)},
415
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()}
418 };
419
420 // peer list
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)
424 {
425 lt::entry::string_type peers;
426 lt::entry::string_type peers6;
427
428 if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
429 {
430 int counter = 0;
431 for (const Peer &peer : asConst(torrentStats.peers))
432 {
433 if (counter++ >= announceReq.numwant)
434 break;
435
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);
440 }
441 }
442
443 replyDict[ANNOUNCE_RESPONSE_PEERS] = peers; // required, even it's empty
444 if (!peers6.empty())
445 replyDict[ANNOUNCE_RESPONSE_PEERS6] = peers6;
446 }
447 else
448 {
449 lt::entry::list_type peerList;
450
451 if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
452 {
453 int counter = 0;
454 for (const Peer &peer : torrentStats.peers)
455 {
456 if (counter++ >= announceReq.numwant)
457 break;
458
459 lt::entry::dictionary_type peerDict =
460 {
461 {ANNOUNCE_RESPONSE_PEERS_IP, peer.address},
462 {ANNOUNCE_RESPONSE_PEERS_PORT, peer.port}
463 };
464
465 if (!announceReq.noPeerId)
466 peerDict[ANNOUNCE_RESPONSE_PEERS_PEER_ID] = peer.peerId.constData();
467
468 peerList.emplace_back(peerDict);
469 }
470 }
471
472 replyDict[ANNOUNCE_RESPONSE_PEERS] = peerList;
473 }
474
475 // bencode
476 QByteArray reply;
477 lt::bencode(std::back_inserter(reply), replyDict);
478 print(reply, Http::CONTENT_TYPE_TXT);
479 }
480