From 94f7a095bb8e9ecbc7ce55dcf6f9d4a9b06f1dcc Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 8 Aug 2019 23:19:53 +0800 Subject: [PATCH 1/2] Implement proper equal operators --- src/base/bittorrent/tracker.cpp | 20 ++++++++++---------- src/base/bittorrent/tracker.h | 5 +++-- src/base/tristatebool.h | 14 +++++++------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/base/bittorrent/tracker.cpp b/src/base/bittorrent/tracker.cpp index e9d140dc5..24fc7b434 100644 --- a/src/base/bittorrent/tracker.cpp +++ b/src/base/bittorrent/tracker.cpp @@ -43,16 +43,6 @@ static const int ANNOUNCE_INTERVAL = 1800; // 30min using namespace BitTorrent; // Peer -bool Peer::operator!=(const Peer &other) const -{ - return uid() != other.uid(); -} - -bool Peer::operator==(const Peer &other) const -{ - return uid() == other.uid(); -} - QString Peer::uid() const { return ip.toString() + ':' + QString::number(port); @@ -69,6 +59,16 @@ lt::entry Peer::toEntry(const bool noPeerId) const return lt::entry(peerMap); } +bool BitTorrent::operator==(const Peer &left, const Peer &right) +{ + return left.uid() == right.uid(); +} + +bool BitTorrent::operator!=(const Peer &left, const Peer &right) +{ + return !(left == right); +} + // Tracker Tracker::Tracker(QObject *parent) diff --git a/src/base/bittorrent/tracker.h b/src/base/bittorrent/tracker.h index b4db4c0ce..58174d0b7 100644 --- a/src/base/bittorrent/tracker.h +++ b/src/base/bittorrent/tracker.h @@ -52,12 +52,13 @@ namespace BitTorrent QByteArray peerId; int port; - bool operator!=(const Peer &other) const; - bool operator==(const Peer &other) const; QString uid() const; lt::entry toEntry(bool noPeerId) const; }; + bool operator==(const Peer &left, const Peer &right); + bool operator!=(const Peer &left, const Peer &right); + struct TrackerAnnounceRequest { QByteArray infoHash; diff --git a/src/base/tristatebool.h b/src/base/tristatebool.h index 086bf4af4..7a74eae9f 100644 --- a/src/base/tristatebool.h +++ b/src/base/tristatebool.h @@ -50,18 +50,18 @@ public: TriStateBool &operator=(const TriStateBool &other) = default; // add constexpr when using C++17 - constexpr bool operator==(const TriStateBool &other) const + constexpr friend bool operator==(const TriStateBool &left, const TriStateBool &right) { - return (m_value == other.m_value); - } - - constexpr bool operator!=(const TriStateBool &other) const - { - return !operator==(other); + return (left.m_value == right.m_value); } private: signed char m_value = -1; // Undefined by default }; +constexpr bool operator!=(const TriStateBool &left, const TriStateBool &right) +{ + return !(left == right); +} + #endif // TRISTATEBOOL_H From 8d0d8e4dcbf2fe43f4637c5ad4d53b17683df515 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 8 Aug 2019 23:21:07 +0800 Subject: [PATCH 2/2] Improve embedded tracker Now it conforms to BEPs more closely. --- src/base/bittorrent/session.cpp | 14 +- src/base/bittorrent/tracker.cpp | 470 ++++++++++++++++++++---------- src/base/bittorrent/tracker.h | 61 ++-- src/base/exceptions.h | 2 +- src/base/http/httperror.cpp | 19 +- src/base/http/httperror.h | 28 +- src/base/http/responsebuilder.cpp | 2 +- src/base/http/types.h | 7 +- src/gui/advancedsettings.cpp | 2 +- src/webui/api/appcontroller.cpp | 4 +- 10 files changed, 401 insertions(+), 208 deletions(-) diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 0974ee5bc..426ee20df 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -909,10 +909,12 @@ bool Session::isTrackerEnabled() const void Session::setTrackerEnabled(const bool enabled) { - if (isTrackerEnabled() != enabled) { - enableTracker(enabled); + if (m_isTrackerEnabled != enabled) m_isTrackerEnabled = enabled; - } + + // call enableTracker() unconditionally, otherwise port change won't trigger + // tracker restart + enableTracker(enabled); } qreal Session::globalMaxRatio() const @@ -1480,13 +1482,9 @@ void Session::enableTracker(const bool enable) if (!m_tracker) m_tracker = new Tracker(this); - if (m_tracker->start()) - LogMsg(tr("Embedded Tracker [ON]"), Log::INFO); - else - LogMsg(tr("Failed to start the embedded tracker!"), Log::CRITICAL); + m_tracker->start(); } else { - LogMsg(tr("Embedded Tracker [OFF]"), Log::INFO); if (m_tracker) delete m_tracker; } diff --git a/src/base/bittorrent/tracker.cpp b/src/base/bittorrent/tracker.cpp index 24fc7b434..5f5d15bc4 100644 --- a/src/base/bittorrent/tracker.cpp +++ b/src/base/bittorrent/tracker.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 Mike Tzou (Chocobo1) * Copyright (C) 2015 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * @@ -32,235 +33,410 @@ #include #include +#include + +#include "base/exceptions.h" +#include "base/global.h" +#include "base/http/httperror.h" #include "base/http/server.h" +#include "base/http/types.h" +#include "base/logger.h" #include "base/preferences.h" -// static limits -static const int MAX_TORRENTS = 100; -static const int MAX_PEERS_PER_TORRENT = 1000; -static const int ANNOUNCE_INTERVAL = 1800; // 30min - -using namespace BitTorrent; - -// Peer -QString Peer::uid() const +namespace { - return ip.toString() + ':' + QString::number(port); + // 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((ipv4 >> 24) & 0xFF)) + .append(static_cast((ipv4 >> 16) & 0xFF)) + .append(static_cast((ipv4 >> 8) & 0xFF)) + .append(static_cast(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(ipv6.c[i])); + return ret; + } + + case QAbstractSocket::UnknownNetworkLayerProtocol: + default: + return {}; + }; + } } -lt::entry Peer::toEntry(const bool noPeerId) const +namespace BitTorrent { - lt::entry::dictionary_type peerMap; - if (!noPeerId) - peerMap["id"] = lt::entry(peerId.toStdString()); - peerMap["ip"] = lt::entry(ip.toString().toStdString()); - peerMap["port"] = lt::entry(port); + // 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()); + } - return lt::entry(peerMap); + bool operator!=(const Peer &left, const Peer &right) + { + return !(left == right); + } + + uint qHash(const Peer &key, const uint seed) + { + return qHash(key.uniqueID(), seed); + } } -bool BitTorrent::operator==(const Peer &left, const Peer &right) +using namespace BitTorrent; + +// TrackerAnnounceRequest +struct Tracker::TrackerAnnounceRequest { - return left.uid() == right.uid(); + 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); } -bool BitTorrent::operator!=(const Peer &left, const Peer &right) +bool Tracker::TorrentStats::removePeer(const Peer &peer) { - return !(left == right); + const auto iter = peers.find(peer); + if (iter == peers.end()) + return false; + + if (iter->isSeeder) + --seeders; + peers.remove(*iter); + return true; } // Tracker - Tracker::Tracker(QObject *parent) : QObject(parent) , m_server(new Http::Server(this, this)) { } -Tracker::~Tracker() -{ - if (m_server->isListening()) - qDebug("Shutting down the embedded tracker..."); - // TODO: Store the torrent list -} - bool Tracker::start() { - const int listenPort = Preferences::instance()->getTrackerPort(); + const QHostAddress ip = QHostAddress::Any; + const int port = Preferences::instance()->getTrackerPort(); if (m_server->isListening()) { - if (m_server->serverPort() == listenPort) { + if (m_server->serverPort() == port) { // Already listening on the right port, just return return true; } + // Wrong port, closing the server m_server->close(); } - qDebug("Starting the embedded tracker..."); // Listen on the predefined port - return m_server->listen(QHostAddress::Any, listenPort); + 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; } Http::Response Tracker::processRequest(const Http::Request &request, const Http::Environment &env) { - clear(); // clear response + clear(); // clear response - //qDebug("Tracker received the following request:\n%s", qUtf8Printable(parser.toString())); - // Is request a GET request? - if (request.method != "GET") { - qDebug("Tracker: Unsupported HTTP request: %s", qUtf8Printable(request.method)); - status(100, "Invalid request type"); + 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(); } - else if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) { - qDebug("Tracker: Unrecognized path: %s", qUtf8Printable(request.path)); - status(100, "Invalid request type"); + catch (const HTTPError &error) { + status(error.statusCode(), error.statusText()); + if (!error.message().isEmpty()) + print(error.message(), Http::CONTENT_TYPE_TXT); } - else { - // OK, this is a GET request - m_request = request; - m_env = env; - respondToAnnounceRequest(); + 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); } return response(); } -void Tracker::respondToAnnounceRequest() +void Tracker::processAnnounceRequest() { const QHash &queryParams = m_request.query; TrackerAnnounceRequest announceReq; - // IP - // Use the "ip" parameter provided from tracker request first, then fall back to client IP if invalid - const QHostAddress paramIP {QString::fromLatin1(queryParams.value("ip"))}; - announceReq.peer.ip = paramIP.isNull() ? m_env.clientAddress : paramIP; + // ip address + announceReq.socketAddress = m_env.clientAddress; + announceReq.claimedAddress = queryParams.value(ANNOUNCE_REQUEST_IP); - // 1. Get info_hash - if (!queryParams.contains("info_hash")) { - qDebug("Tracker: Missing info_hash"); - status(101, "Missing info_hash"); - return; - } - announceReq.infoHash = queryParams.value("info_hash"); - // info_hash cannot be longer than 20 bytes - /*if (annonce_req.info_hash.toLatin1().length() > 20) { - qDebug("Tracker: Info_hash is not 20 byte long: %s (%d)", qUtf8Printable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length()); - status(150, "Invalid infohash"); - return; - }*/ + // 1. info_hash + const auto infoHashIter = queryParams.find(ANNOUNCE_REQUEST_INFO_HASH); + if (infoHashIter == queryParams.end()) + throw TrackerError("Missing \"info_hash\" parameter"); - // 2. Get peer ID - if (!queryParams.contains("peer_id")) { - qDebug("Tracker: Missing peer_id"); - status(102, "Missing peer_id"); - return; - } - announceReq.peer.peerId = queryParams.value("peer_id"); - // peer_id cannot be longer than 20 bytes - /*if (annonce_req.peer.peer_id.length() > 20) { - qDebug("Tracker: peer_id is not 20 byte long: %s", qUtf8Printable(annonce_req.peer.peer_id)); - status(151, "Invalid peerid"); - return; - }*/ + const InfoHash infoHash(infoHashIter->toHex()); + if (!infoHash.isValid()) + throw TrackerError("Invalid \"info_hash\" parameter"); - // 3. Get port - if (!queryParams.contains("port")) { - qDebug("Tracker: Missing port"); - status(103, "Missing port"); - return; - } - bool ok = false; - announceReq.peer.port = queryParams.value("port").toInt(&ok); - if (!ok || (announceReq.peer.port < 0) || (announceReq.peer.port > 65535)) { - qDebug("Tracker: Invalid port number (%d)", announceReq.peer.port); - status(103, "Missing port"); - return; - } + announceReq.infoHash = infoHash; - // 4. Get event - announceReq.event = ""; - if (queryParams.contains("event")) { - announceReq.event = queryParams.value("event"); - qDebug("Tracker: event is %s", qUtf8Printable(announceReq.event)); - } + // 2. peer_id + const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID); + if (peerIdIter == queryParams.end()) + throw TrackerError("Missing \"peer_id\" parameter"); - // 5. Get numwant - announceReq.numwant = 50; - if (queryParams.contains("numwant")) { - int tmp = queryParams.value("numwant").toInt(); - if (tmp > 0) { - qDebug("Tracker: numwant = %d", tmp); - announceReq.numwant = tmp; - } - } + if (peerIdIter->size() > PEER_ID_SIZE) + throw TrackerError("Invalid \"peer_id\" parameter"); - // 6. no_peer_id (extension) - announceReq.noPeerId = false; - if (queryParams.contains("no_peer_id")) - announceReq.noPeerId = true; + announceReq.peer.peerId = *peerIdIter; - // 7. TODO: support "compact" extension + // 3. port + const auto portIter = queryParams.find(ANNOUNCE_REQUEST_PORT); + if (portIter == queryParams.end()) + throw TrackerError("Missing \"port\" parameter"); - // Done parsing, now let's reply - if (announceReq.event == "stopped") { + 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((announceReq.peer.port >> 8) & 0xFF)) + .append(static_cast(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) { unregisterPeer(announceReq); } else { - registerPeer(announceReq); - replyWithPeerList(announceReq); + throw TrackerError("Invalid \"event\" parameter"); } } void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq) { - if (announceReq.peer.port == 0) return; - if (!m_torrents.contains(announceReq.infoHash)) { - // Unknown torrent - if (m_torrents.size() == MAX_TORRENTS) { - // Reached max size, remove a random torrent + // Reached max size, remove a random torrent + if (m_torrents.size() >= MAX_TORRENTS) m_torrents.erase(m_torrents.begin()); - } } - // Register the user - PeerList &peers = m_torrents[announceReq.infoHash]; - if (!peers.contains(announceReq.peer.uid())) { - // Unknown peer - if (peers.size() == MAX_PEERS_PER_TORRENT) { - // Too many peers, remove a random one - peers.erase(peers.begin()); - } - } - peers[announceReq.peer.uid()] = announceReq.peer; + m_torrents[announceReq.infoHash].setPeer(announceReq.peer); } void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq) { - if (announceReq.peer.port == 0) return; + const auto torrentStatsIter = m_torrents.find(announceReq.infoHash); + if (torrentStatsIter == m_torrents.end()) + return; - if (m_torrents[announceReq.infoHash].remove(announceReq.peer.uid()) > 0) - qDebug("Tracker: Peer stopped downloading, deleting it from the list"); + torrentStatsIter->removePeer(announceReq.peer); + + if (torrentStatsIter->peers.isEmpty()) + m_torrents.erase(torrentStatsIter); } -void Tracker::replyWithPeerList(const TrackerAnnounceRequest &announceReq) +void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq) { - // Prepare the entry for bencoding - lt::entry::dictionary_type replyDict; - replyDict["interval"] = lt::entry(ANNOUNCE_INTERVAL); + 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(); - lt::entry::list_type peerList; - for (const Peer &p : m_torrents.value(announceReq.infoHash)) - peerList.push_back(p.toEntry(announceReq.noPeerId)); - replyDict["peers"] = lt::entry(peerList); + peerList.emplace_back(peerDict); + } + + replyDict[ANNOUNCE_RESPONSE_PEERS] = peerList; + } - const lt::entry replyEntry(replyDict); // bencode QByteArray reply; - lt::bencode(std::back_inserter(reply), replyEntry); - qDebug("Tracker: reply with the following bencoded data:\n %s", reply.constData()); - - // HTTP reply + lt::bencode(std::back_inserter(reply), replyDict); print(reply, Http::CONTENT_TYPE_TXT); } diff --git a/src/base/bittorrent/tracker.h b/src/base/bittorrent/tracker.h index 58174d0b7..a843f5f58 100644 --- a/src/base/bittorrent/tracker.h +++ b/src/base/bittorrent/tracker.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 Mike Tzou (Chocobo1) * Copyright (C) 2015 Vladimir Golovnev * Copyright (C) 2010 Christophe Dumez * @@ -30,12 +31,15 @@ #ifndef BITTORRENT_TRACKER_H #define BITTORRENT_TRACKER_H -#include +#include + +#include #include #include -#include +#include +#include "base/bittorrent/infohash.h" #include "base/http/irequesthandler.h" #include "base/http/responsebuilder.h" @@ -48,55 +52,58 @@ namespace BitTorrent { struct Peer { - QHostAddress ip; QByteArray peerId; - int port; + ushort port = 0; // self-claimed by peer, might not be the same as socket port + bool isSeeder = false; + + // caching precomputed values + lt::entry::string_type address; + lt::entry::string_type endpoint; - QString uid() const; - lt::entry toEntry(bool noPeerId) const; + QByteArray uniqueID() const; }; bool operator==(const Peer &left, const Peer &right); bool operator!=(const Peer &left, const Peer &right); + uint qHash(const Peer &key, uint seed); - struct TrackerAnnounceRequest - { - QByteArray infoHash; - QString event; - int numwant; - Peer peer; - // Extensions - bool noPeerId; - }; - - typedef QHash PeerList; - typedef QHash TorrentList; - - /* Basic Bittorrent tracker implementation in Qt */ - /* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */ + // *Basic* Bittorrent tracker implementation + // [BEP-3] The BitTorrent Protocol Specification + // also see: https://wiki.theory.org/index.php/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocol class Tracker : public QObject, public Http::IRequestHandler, private Http::ResponseBuilder { Q_OBJECT Q_DISABLE_COPY(Tracker) + struct TrackerAnnounceRequest; + + struct TorrentStats + { + qint64 seeders = 0; + QSet peers; + + void setPeer(const Peer &peer); + bool removePeer(const Peer &peer); + }; + public: explicit Tracker(QObject *parent = nullptr); - ~Tracker(); bool start(); - Http::Response processRequest(const Http::Request &request, const Http::Environment &env) override; private: - void respondToAnnounceRequest(); + Http::Response processRequest(const Http::Request &request, const Http::Environment &env) override; + void processAnnounceRequest(); + void registerPeer(const TrackerAnnounceRequest &announceReq); void unregisterPeer(const TrackerAnnounceRequest &announceReq); - void replyWithPeerList(const TrackerAnnounceRequest &announceReq); + void prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq); Http::Server *m_server; - TorrentList m_torrents; - Http::Request m_request; Http::Environment m_env; + + QHash m_torrents; }; } diff --git a/src/base/exceptions.h b/src/base/exceptions.h index 3b4a67d1e..4b3ef77a4 100644 --- a/src/base/exceptions.h +++ b/src/base/exceptions.h @@ -34,6 +34,6 @@ class RuntimeError : public std::runtime_error { public: - explicit RuntimeError(const QString &message = ""); + explicit RuntimeError(const QString &message = {}); QString message() const; }; diff --git a/src/base/http/httperror.cpp b/src/base/http/httperror.cpp index d75d4f79a..7f66fb627 100644 --- a/src/base/http/httperror.cpp +++ b/src/base/http/httperror.cpp @@ -28,7 +28,7 @@ #include "httperror.h" -HTTPError::HTTPError(int statusCode, const QString &statusText, const QString &message) +HTTPError::HTTPError(const int statusCode, const QString &statusText, const QString &message) : RuntimeError {message} , m_statusCode {statusCode} , m_statusText {statusText} @@ -50,8 +50,8 @@ BadRequestHTTPError::BadRequestHTTPError(const QString &message) { } -ConflictHTTPError::ConflictHTTPError(const QString &message) - : HTTPError(409, QLatin1String("Conflict"), message) +UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message) + : HTTPError(401, QLatin1String("Unauthorized"), message) { } @@ -65,13 +65,18 @@ NotFoundHTTPError::NotFoundHTTPError(const QString &message) { } -UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message) - : HTTPError(415, QLatin1String("Unsupported Media Type"), message) +MethodNotAllowedHTTPError::MethodNotAllowedHTTPError(const QString &message) + : HTTPError(405, QLatin1String("Method Not Allowed"), message) { } -UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message) - : HTTPError(401, QLatin1String("Unauthorized"), message) +ConflictHTTPError::ConflictHTTPError(const QString &message) + : HTTPError(409, QLatin1String("Conflict"), message) +{ +} + +UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message) + : HTTPError(415, QLatin1String("Unsupported Media Type"), message) { } diff --git a/src/base/http/httperror.h b/src/base/http/httperror.h index 88fd716d1..ab597ea64 100644 --- a/src/base/http/httperror.h +++ b/src/base/http/httperror.h @@ -33,7 +33,7 @@ class HTTPError : public RuntimeError { public: - HTTPError(int statusCode, const QString &statusText, const QString &message = ""); + HTTPError(int statusCode, const QString &statusText, const QString &message = {}); int statusCode() const; QString statusText() const; @@ -46,41 +46,47 @@ private: class BadRequestHTTPError : public HTTPError { public: - explicit BadRequestHTTPError(const QString &message = ""); + explicit BadRequestHTTPError(const QString &message = {}); +}; + +class UnauthorizedHTTPError : public HTTPError +{ +public: + explicit UnauthorizedHTTPError(const QString &message = {}); }; class ForbiddenHTTPError : public HTTPError { public: - explicit ForbiddenHTTPError(const QString &message = ""); + explicit ForbiddenHTTPError(const QString &message = {}); }; class NotFoundHTTPError : public HTTPError { public: - explicit NotFoundHTTPError(const QString &message = ""); + explicit NotFoundHTTPError(const QString &message = {}); }; -class ConflictHTTPError : public HTTPError +class MethodNotAllowedHTTPError : public HTTPError { public: - explicit ConflictHTTPError(const QString &message = ""); + explicit MethodNotAllowedHTTPError(const QString &message = {}); }; -class UnsupportedMediaTypeHTTPError : public HTTPError +class ConflictHTTPError : public HTTPError { public: - explicit UnsupportedMediaTypeHTTPError(const QString &message = ""); + explicit ConflictHTTPError(const QString &message = {}); }; -class UnauthorizedHTTPError : public HTTPError +class UnsupportedMediaTypeHTTPError : public HTTPError { public: - explicit UnauthorizedHTTPError(const QString &message = ""); + explicit UnsupportedMediaTypeHTTPError(const QString &message = {}); }; class InternalServerErrorHTTPError : public HTTPError { public: - explicit InternalServerErrorHTTPError(const QString &message = ""); + explicit InternalServerErrorHTTPError(const QString &message = {}); }; diff --git a/src/base/http/responsebuilder.cpp b/src/base/http/responsebuilder.cpp index 13ca09872..fd136a224 100644 --- a/src/base/http/responsebuilder.cpp +++ b/src/base/http/responsebuilder.cpp @@ -32,7 +32,7 @@ using namespace Http; void ResponseBuilder::status(const uint code, const QString &text) { - m_response.status = ResponseStatus(code, text); + m_response.status = {code, text}; } void ResponseBuilder::header(const QString &name, const QString &value) diff --git a/src/base/http/types.h b/src/base/http/types.h index e89970c2c..d63d1bbb1 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -107,8 +107,6 @@ namespace Http { uint code; QString text; - - ResponseStatus(uint code = 200, const QString &text = "OK"): code(code), text(text) {} }; struct Response @@ -117,7 +115,10 @@ namespace Http QStringMap headers; QByteArray content; - Response(uint code = 200, const QString &text = "OK"): status(code, text) {} + Response(uint code = 200, const QString &text = "OK") + : status {code, text} + { + } }; } diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index 303857d07..21621f771 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -228,8 +228,8 @@ void AdvancedSettings::saveAdvancedSettings() pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked()); // Tracker - session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked()); pref->setTrackerPort(m_spinBoxTrackerPort.value()); + session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked()); // Choking algorithm session->setChokingAlgorithm(static_cast(m_comboBoxChokingAlgorithm.currentIndex())); // Seed choking algorithm diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index c3c45227f..3cfd724be 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -713,10 +713,10 @@ void AppController::setPreferencesAction() if (hasKey("enable_multi_connections_from_same_ip")) session->setMultiConnectionsPerIpEnabled(it.value().toBool()); // Embedded tracker - if (hasKey("enable_embedded_tracker")) - session->setTrackerEnabled(it.value().toBool()); if (hasKey("embedded_tracker_port")) pref->setTrackerPort(it.value().toInt()); + if (hasKey("enable_embedded_tracker")) + session->setTrackerEnabled(it.value().toBool()); // Choking algorithm if (hasKey("upload_slots_behavior")) session->setChokingAlgorithm(static_cast(it.value().toInt()));