Browse Source

Improve embedded tracker

Now it conforms to BEPs more closely.
adaptive-webui-19844
Chocobo1 5 years ago
parent
commit
8d0d8e4dcb
No known key found for this signature in database
GPG Key ID: 210D9C873253A68C
  1. 14
      src/base/bittorrent/session.cpp
  2. 470
      src/base/bittorrent/tracker.cpp
  3. 61
      src/base/bittorrent/tracker.h
  4. 2
      src/base/exceptions.h
  5. 19
      src/base/http/httperror.cpp
  6. 28
      src/base/http/httperror.h
  7. 2
      src/base/http/responsebuilder.cpp
  8. 7
      src/base/http/types.h
  9. 2
      src/gui/advancedsettings.cpp
  10. 4
      src/webui/api/appcontroller.cpp

14
src/base/bittorrent/session.cpp

@ -909,10 +909,12 @@ bool Session::isTrackerEnabled() const
void Session::setTrackerEnabled(const bool enabled) void Session::setTrackerEnabled(const bool enabled)
{ {
if (isTrackerEnabled() != enabled) { if (m_isTrackerEnabled != enabled)
enableTracker(enabled);
m_isTrackerEnabled = enabled; m_isTrackerEnabled = enabled;
}
// call enableTracker() unconditionally, otherwise port change won't trigger
// tracker restart
enableTracker(enabled);
} }
qreal Session::globalMaxRatio() const qreal Session::globalMaxRatio() const
@ -1480,13 +1482,9 @@ void Session::enableTracker(const bool enable)
if (!m_tracker) if (!m_tracker)
m_tracker = new Tracker(this); m_tracker = new Tracker(this);
if (m_tracker->start()) m_tracker->start();
LogMsg(tr("Embedded Tracker [ON]"), Log::INFO);
else
LogMsg(tr("Failed to start the embedded tracker!"), Log::CRITICAL);
} }
else { else {
LogMsg(tr("Embedded Tracker [OFF]"), Log::INFO);
if (m_tracker) if (m_tracker)
delete m_tracker; delete m_tracker;
} }

470
src/base/bittorrent/tracker.cpp

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2019 Mike Tzou (Chocobo1)
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
@ -32,235 +33,410 @@
#include <libtorrent/bencode.hpp> #include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp> #include <libtorrent/entry.hpp>
#include <QHostAddress>
#include "base/exceptions.h"
#include "base/global.h"
#include "base/http/httperror.h"
#include "base/http/server.h" #include "base/http/server.h"
#include "base/http/types.h"
#include "base/logger.h"
#include "base/preferences.h" #include "base/preferences.h"
// static limits namespace
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
{ {
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<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 {};
};
}
} }
lt::entry Peer::toEntry(const bool noPeerId) const namespace BitTorrent
{ {
lt::entry::dictionary_type peerMap; // Peer
if (!noPeerId) QByteArray Peer::uniqueID() const
peerMap["id"] = lt::entry(peerId.toStdString()); {
peerMap["ip"] = lt::entry(ip.toString().toStdString()); return (QByteArray::fromStdString(address) + ':' + QByteArray::number(port));
peerMap["port"] = lt::entry(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::Tracker(QObject *parent) Tracker::Tracker(QObject *parent)
: QObject(parent) : QObject(parent)
, m_server(new Http::Server(this, this)) , 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() 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->isListening()) {
if (m_server->serverPort() == listenPort) { if (m_server->serverPort() == port) {
// Already listening on the right port, just return // Already listening on the right port, just return
return true; return true;
} }
// Wrong port, closing the server // Wrong port, closing the server
m_server->close(); m_server->close();
} }
qDebug("Starting the embedded tracker...");
// Listen on the predefined port // 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) 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())); m_request = request;
// Is request a GET request? m_env = env;
if (request.method != "GET") {
qDebug("Tracker: Unsupported HTTP request: %s", qUtf8Printable(request.method)); status(200);
status(100, "Invalid request type");
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)) { catch (const HTTPError &error) {
qDebug("Tracker: Unrecognized path: %s", qUtf8Printable(request.path)); status(error.statusCode(), error.statusText());
status(100, "Invalid request type"); if (!error.message().isEmpty())
print(error.message(), Http::CONTENT_TYPE_TXT);
} }
else { catch (const TrackerError &error) {
// OK, this is a GET request clear(); // clear response
m_request = request; status(200);
m_env = env;
respondToAnnounceRequest(); 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(); return response();
} }
void Tracker::respondToAnnounceRequest() void Tracker::processAnnounceRequest()
{ {
const QHash<QString, QByteArray> &queryParams = m_request.query; const QHash<QString, QByteArray> &queryParams = m_request.query;
TrackerAnnounceRequest announceReq; TrackerAnnounceRequest announceReq;
// IP // ip address
// Use the "ip" parameter provided from tracker request first, then fall back to client IP if invalid announceReq.socketAddress = m_env.clientAddress;
const QHostAddress paramIP {QString::fromLatin1(queryParams.value("ip"))}; announceReq.claimedAddress = queryParams.value(ANNOUNCE_REQUEST_IP);
announceReq.peer.ip = paramIP.isNull() ? m_env.clientAddress : paramIP;
// 1. Get info_hash // 1. info_hash
if (!queryParams.contains("info_hash")) { const auto infoHashIter = queryParams.find(ANNOUNCE_REQUEST_INFO_HASH);
qDebug("Tracker: Missing info_hash"); if (infoHashIter == queryParams.end())
status(101, "Missing info_hash"); throw TrackerError("Missing \"info_hash\" parameter");
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;
}*/
// 2. Get peer ID const InfoHash infoHash(infoHashIter->toHex());
if (!queryParams.contains("peer_id")) { if (!infoHash.isValid())
qDebug("Tracker: Missing peer_id"); throw TrackerError("Invalid \"info_hash\" parameter");
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;
}*/
// 3. Get port announceReq.infoHash = infoHash;
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;
}
// 4. Get event // 2. peer_id
announceReq.event = ""; const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
if (queryParams.contains("event")) { if (peerIdIter == queryParams.end())
announceReq.event = queryParams.value("event"); throw TrackerError("Missing \"peer_id\" parameter");
qDebug("Tracker: event is %s", qUtf8Printable(announceReq.event));
}
// 5. Get numwant if (peerIdIter->size() > PEER_ID_SIZE)
announceReq.numwant = 50; throw TrackerError("Invalid \"peer_id\" parameter");
if (queryParams.contains("numwant")) {
int tmp = queryParams.value("numwant").toInt();
if (tmp > 0) {
qDebug("Tracker: numwant = %d", tmp);
announceReq.numwant = tmp;
}
}
// 6. no_peer_id (extension) announceReq.peer.peerId = *peerIdIter;
announceReq.noPeerId = false;
if (queryParams.contains("no_peer_id"))
announceReq.noPeerId = true;
// 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 const ushort portNum = portIter->toUShort();
if (announceReq.event == "stopped") { 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) {
unregisterPeer(announceReq); unregisterPeer(announceReq);
} }
else { else {
registerPeer(announceReq); throw TrackerError("Invalid \"event\" parameter");
replyWithPeerList(announceReq);
} }
} }
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq) void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{ {
if (announceReq.peer.port == 0) return;
if (!m_torrents.contains(announceReq.infoHash)) { if (!m_torrents.contains(announceReq.infoHash)) {
// Unknown torrent // Reached max size, remove a random torrent
if (m_torrents.size() == MAX_TORRENTS) { if (m_torrents.size() >= MAX_TORRENTS)
// Reached max size, remove a random torrent
m_torrents.erase(m_torrents.begin()); m_torrents.erase(m_torrents.begin());
}
} }
// Register the user m_torrents[announceReq.infoHash].setPeer(announceReq.peer);
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;
} }
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq) 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) torrentStatsIter->removePeer(announceReq.peer);
qDebug("Tracker: Peer stopped downloading, deleting it from the list");
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 const TorrentStats &torrentStats = m_torrents[announceReq.infoHash];
lt::entry::dictionary_type replyDict;
replyDict["interval"] = lt::entry(ANNOUNCE_INTERVAL); 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; peerList.emplace_back(peerDict);
for (const Peer &p : m_torrents.value(announceReq.infoHash)) }
peerList.push_back(p.toEntry(announceReq.noPeerId));
replyDict["peers"] = lt::entry(peerList); replyDict[ANNOUNCE_RESPONSE_PEERS] = peerList;
}
const lt::entry replyEntry(replyDict);
// bencode // bencode
QByteArray reply; QByteArray reply;
lt::bencode(std::back_inserter(reply), replyEntry); lt::bencode(std::back_inserter(reply), replyDict);
qDebug("Tracker: reply with the following bencoded data:\n %s", reply.constData());
// HTTP reply
print(reply, Http::CONTENT_TYPE_TXT); print(reply, Http::CONTENT_TYPE_TXT);
} }

61
src/base/bittorrent/tracker.h

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2019 Mike Tzou (Chocobo1)
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* *
@ -30,12 +31,15 @@
#ifndef BITTORRENT_TRACKER_H #ifndef BITTORRENT_TRACKER_H
#define BITTORRENT_TRACKER_H #define BITTORRENT_TRACKER_H
#include <libtorrent/fwd.hpp> #include <string>
#include <libtorrent/entry.hpp>
#include <QHash> #include <QHash>
#include <QObject> #include <QObject>
#include <QHostAddress> #include <QSet>
#include "base/bittorrent/infohash.h"
#include "base/http/irequesthandler.h" #include "base/http/irequesthandler.h"
#include "base/http/responsebuilder.h" #include "base/http/responsebuilder.h"
@ -48,55 +52,58 @@ namespace BitTorrent
{ {
struct Peer struct Peer
{ {
QHostAddress ip;
QByteArray peerId; 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; QByteArray uniqueID() const;
lt::entry toEntry(bool noPeerId) const;
}; };
bool operator==(const Peer &left, const Peer &right); bool operator==(const Peer &left, const Peer &right);
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 // *Basic* Bittorrent tracker implementation
{ // [BEP-3] The BitTorrent Protocol Specification
QByteArray infoHash; // also see: https://wiki.theory.org/index.php/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocol
QString event;
int numwant;
Peer peer;
// Extensions
bool noPeerId;
};
typedef QHash<QString, Peer> PeerList;
typedef QHash<QByteArray, PeerList> TorrentList;
/* Basic Bittorrent tracker implementation in Qt */
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
class Tracker : public QObject, public Http::IRequestHandler, private Http::ResponseBuilder class Tracker : public QObject, public Http::IRequestHandler, private Http::ResponseBuilder
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(Tracker) Q_DISABLE_COPY(Tracker)
struct TrackerAnnounceRequest;
struct TorrentStats
{
qint64 seeders = 0;
QSet<Peer> peers;
void setPeer(const Peer &peer);
bool removePeer(const Peer &peer);
};
public: public:
explicit Tracker(QObject *parent = nullptr); explicit Tracker(QObject *parent = nullptr);
~Tracker();
bool start(); bool start();
Http::Response processRequest(const Http::Request &request, const Http::Environment &env) override;
private: private:
void respondToAnnounceRequest(); Http::Response processRequest(const Http::Request &request, const Http::Environment &env) override;
void processAnnounceRequest();
void registerPeer(const TrackerAnnounceRequest &announceReq); void registerPeer(const TrackerAnnounceRequest &announceReq);
void unregisterPeer(const TrackerAnnounceRequest &announceReq); void unregisterPeer(const TrackerAnnounceRequest &announceReq);
void replyWithPeerList(const TrackerAnnounceRequest &announceReq); void prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq);
Http::Server *m_server; Http::Server *m_server;
TorrentList m_torrents;
Http::Request m_request; Http::Request m_request;
Http::Environment m_env; Http::Environment m_env;
QHash<InfoHash, TorrentStats> m_torrents;
}; };
} }

2
src/base/exceptions.h

@ -34,6 +34,6 @@
class RuntimeError : public std::runtime_error class RuntimeError : public std::runtime_error
{ {
public: public:
explicit RuntimeError(const QString &message = ""); explicit RuntimeError(const QString &message = {});
QString message() const; QString message() const;
}; };

19
src/base/http/httperror.cpp

@ -28,7 +28,7 @@
#include "httperror.h" #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} : RuntimeError {message}
, m_statusCode {statusCode} , m_statusCode {statusCode}
, m_statusText {statusText} , m_statusText {statusText}
@ -50,8 +50,8 @@ BadRequestHTTPError::BadRequestHTTPError(const QString &message)
{ {
} }
ConflictHTTPError::ConflictHTTPError(const QString &message) UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message)
: HTTPError(409, QLatin1String("Conflict"), message) : HTTPError(401, QLatin1String("Unauthorized"), message)
{ {
} }
@ -65,13 +65,18 @@ NotFoundHTTPError::NotFoundHTTPError(const QString &message)
{ {
} }
UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message) MethodNotAllowedHTTPError::MethodNotAllowedHTTPError(const QString &message)
: HTTPError(415, QLatin1String("Unsupported Media Type"), message) : HTTPError(405, QLatin1String("Method Not Allowed"), message)
{ {
} }
UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message) ConflictHTTPError::ConflictHTTPError(const QString &message)
: HTTPError(401, QLatin1String("Unauthorized"), message) : HTTPError(409, QLatin1String("Conflict"), message)
{
}
UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message)
: HTTPError(415, QLatin1String("Unsupported Media Type"), message)
{ {
} }

28
src/base/http/httperror.h

@ -33,7 +33,7 @@
class HTTPError : public RuntimeError class HTTPError : public RuntimeError
{ {
public: public:
HTTPError(int statusCode, const QString &statusText, const QString &message = ""); HTTPError(int statusCode, const QString &statusText, const QString &message = {});
int statusCode() const; int statusCode() const;
QString statusText() const; QString statusText() const;
@ -46,41 +46,47 @@ private:
class BadRequestHTTPError : public HTTPError class BadRequestHTTPError : public HTTPError
{ {
public: 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 class ForbiddenHTTPError : public HTTPError
{ {
public: public:
explicit ForbiddenHTTPError(const QString &message = ""); explicit ForbiddenHTTPError(const QString &message = {});
}; };
class NotFoundHTTPError : public HTTPError class NotFoundHTTPError : public HTTPError
{ {
public: public:
explicit NotFoundHTTPError(const QString &message = ""); explicit NotFoundHTTPError(const QString &message = {});
}; };
class ConflictHTTPError : public HTTPError class MethodNotAllowedHTTPError : public HTTPError
{ {
public: public:
explicit ConflictHTTPError(const QString &message = ""); explicit MethodNotAllowedHTTPError(const QString &message = {});
}; };
class UnsupportedMediaTypeHTTPError : public HTTPError class ConflictHTTPError : public HTTPError
{ {
public: public:
explicit UnsupportedMediaTypeHTTPError(const QString &message = ""); explicit ConflictHTTPError(const QString &message = {});
}; };
class UnauthorizedHTTPError : public HTTPError class UnsupportedMediaTypeHTTPError : public HTTPError
{ {
public: public:
explicit UnauthorizedHTTPError(const QString &message = ""); explicit UnsupportedMediaTypeHTTPError(const QString &message = {});
}; };
class InternalServerErrorHTTPError : public HTTPError class InternalServerErrorHTTPError : public HTTPError
{ {
public: public:
explicit InternalServerErrorHTTPError(const QString &message = ""); explicit InternalServerErrorHTTPError(const QString &message = {});
}; };

2
src/base/http/responsebuilder.cpp

@ -32,7 +32,7 @@ using namespace Http;
void ResponseBuilder::status(const uint code, const QString &text) 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) void ResponseBuilder::header(const QString &name, const QString &value)

7
src/base/http/types.h

@ -107,8 +107,6 @@ namespace Http
{ {
uint code; uint code;
QString text; QString text;
ResponseStatus(uint code = 200, const QString &text = "OK"): code(code), text(text) {}
}; };
struct Response struct Response
@ -117,7 +115,10 @@ namespace Http
QStringMap headers; QStringMap headers;
QByteArray content; QByteArray content;
Response(uint code = 200, const QString &text = "OK"): status(code, text) {} Response(uint code = 200, const QString &text = "OK")
: status {code, text}
{
}
}; };
} }

2
src/gui/advancedsettings.cpp

@ -228,8 +228,8 @@ void AdvancedSettings::saveAdvancedSettings()
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked()); pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
// Tracker // Tracker
session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked());
pref->setTrackerPort(m_spinBoxTrackerPort.value()); pref->setTrackerPort(m_spinBoxTrackerPort.value());
session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked());
// Choking algorithm // Choking algorithm
session->setChokingAlgorithm(static_cast<BitTorrent::ChokingAlgorithm>(m_comboBoxChokingAlgorithm.currentIndex())); session->setChokingAlgorithm(static_cast<BitTorrent::ChokingAlgorithm>(m_comboBoxChokingAlgorithm.currentIndex()));
// Seed choking algorithm // Seed choking algorithm

4
src/webui/api/appcontroller.cpp

@ -713,10 +713,10 @@ void AppController::setPreferencesAction()
if (hasKey("enable_multi_connections_from_same_ip")) if (hasKey("enable_multi_connections_from_same_ip"))
session->setMultiConnectionsPerIpEnabled(it.value().toBool()); session->setMultiConnectionsPerIpEnabled(it.value().toBool());
// Embedded tracker // Embedded tracker
if (hasKey("enable_embedded_tracker"))
session->setTrackerEnabled(it.value().toBool());
if (hasKey("embedded_tracker_port")) if (hasKey("embedded_tracker_port"))
pref->setTrackerPort(it.value().toInt()); pref->setTrackerPort(it.value().toInt());
if (hasKey("enable_embedded_tracker"))
session->setTrackerEnabled(it.value().toBool());
// Choking algorithm // Choking algorithm
if (hasKey("upload_slots_behavior")) if (hasKey("upload_slots_behavior"))
session->setChokingAlgorithm(static_cast<BitTorrent::ChokingAlgorithm>(it.value().toInt())); session->setChokingAlgorithm(static_cast<BitTorrent::ChokingAlgorithm>(it.value().toInt()));

Loading…
Cancel
Save