Merge pull request #3122 from Roardom/manual-announce-bencoding

(Update) Manually bencode the announce response
This commit is contained in:
HDVinnie
2023-09-08 05:23:42 -04:00
committed by GitHub
4 changed files with 47 additions and 47 deletions

View File

@@ -18,7 +18,6 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use App\Exceptions\TrackerException;
use App\Helpers\Bencode;
use App\Models\BlacklistClient;
use App\Models\FreeleechToken;
use App\Models\Group;
@@ -91,7 +90,7 @@ class AnnounceController extends Controller
*/
public function index(Request $request, string $passkey): ?Response
{
$repDict = null;
$response = null;
try {
// Check client.
@@ -129,14 +128,14 @@ class AnnounceController extends Controller
}
// Generate A Response For The Torrent Client.
$repDict = $this->generateSuccessAnnounceResponse($queries, $torrent, $user);
$response = $this->generateSuccessAnnounceResponse($queries, $torrent, $user);
// Process Annnounce Job.
$this->processAnnounceJob($queries, $user, $group, $torrent);
} catch (TrackerException $exception) {
$repDict = $this->generateFailedAnnounceResponse($exception);
$response = $this->generateFailedAnnounceResponse($exception);
} finally {
return $this->sendFinalAnnounceResponse($repDict);
return $this->sendFinalAnnounceResponse($response);
}
}
@@ -471,9 +470,9 @@ class AnnounceController extends Controller
// Detect broken (namely qBittorrent) clients sending duplicate announces
// and eliminate them from screwing up stats.
$duplicateAnnounceKey = 'announce-lock:'.$user->id.'-'.$torrent->id.'-'.base64_decode($queries['peer_id']).'-'.$event;
$duplicateAnnounceKey = config('cache.prefix').'announce-lock:'.$user->id.'-'.$torrent->id.'-'.base64_decode($queries['peer_id']).'-'.$event;
$lastAnnouncedAt = Redis::command('SET', [$duplicateAnnounceKey, $now, 'NX', 'GET', 'EX', '30']);
$lastAnnouncedAt = Redis::connection('announce')->command('SET', [$duplicateAnnounceKey, $now, 'NX', 'GET', 'EX', '30']);
if ($lastAnnouncedAt !== null) {
throw new TrackerException(162, [':elapsed' => $now - $lastAnnouncedAt]);
@@ -481,16 +480,16 @@ class AnnounceController extends Controller
// Block clients disrespecting the min interval
$lastAnnouncedKey = 'peer-last-announced:'.$user->id.'-'.$torrent->id.'-'.base64_decode($queries['peer_id']);
$lastAnnouncedKey = config('cache.prefix').'peer-last-announced:'.$user->id.'-'.$torrent->id.'-'.base64_decode($queries['peer_id']);
$randomMinInterval = intdiv(random_int(85, 95) * self::MIN, 100);
$lastAnnouncedAt = Redis::command('SET', [$lastAnnouncedKey, $now, 'NX', 'GET', 'EX', $randomMinInterval]);
$lastAnnouncedAt = Redis::connection('announce')->command('SET', [$lastAnnouncedKey, $now, 'NX', 'GET', 'EX', $randomMinInterval]);
// Delete the timer if the user paused the torrent, and it's been at
// least 5 minutes since they last announced.
if ($event === 'stopped' && $lastAnnouncedAt < $now - 5 * 60) {
Redis::command('DEL', [$lastAnnouncedKey]);
Redis::connection('announce')->command('DEL', [$lastAnnouncedKey]);
} elseif ($lastAnnouncedAt !== null && ! \in_array($event, ['completed', 'stopped'])) {
throw new TrackerException(162, [':elapsed' => $now - $lastAnnouncedAt]);
}
@@ -560,18 +559,21 @@ class AnnounceController extends Controller
*
* @throws Exception
*/
private function generateSuccessAnnounceResponse($queries, $torrent, $user): array
private function generateSuccessAnnounceResponse($queries, $torrent, $user): string
{
// Build Response For Bittorrent Client
$repDict = [
'interval' => random_int(self::MIN, self::MAX),
'min interval' => self::MIN,
'complete' => (int) $torrent->seeders,
'incomplete' => (int) $torrent->leechers,
'downloaded' => (int) $torrent->times_completed,
'peers' => '',
'peers6' => '',
];
// Keys must be ordered alphabetically
$response = 'd8:completei'
.$torrent->seeders
.'e10:downloadedi'
.$torrent->times_completed
.'e10:incompletei'
.$torrent->leechers
.'e8:intervali'
.random_int(self::MIN, self::MAX)
.'e12:min intervali'
.self::MIN
.'e';
/**
* For non `stopped` event only
@@ -590,15 +592,29 @@ class AnnounceController extends Controller
->only(['ip', 'port'])
->toArray();
$peersIpv4 = '';
$peersIpv6 = '';
foreach ($peers as $peer) {
if (isset($peer['ip'], $peer['port'])) {
$peer_insert_field = filter_var($peer['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? 'peers' : 'peers6';
$repDict[$peer_insert_field] .= inet_pton($peer['ip']).pack('n', (int) $peer['port']);
if (filter_var($peer['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$peersIpv4 .= inet_pton($peer['ip']).pack('n', (int) $peer['port']);
} else {
$peersIpv6 .= inet_pton($peer['ip']).pack('n', (int) $peer['port']);
}
}
}
$response .= '5:peers'.\strlen($peersIpv4).':'.$peersIpv4;
if ($peersIpv6 !== '') {
$response .= '6:peers6'.\strlen($peersIpv6).':'.$peersIpv6;
}
}
return $repDict;
$response .= 'e';
return $response;
}
/**
@@ -764,19 +780,18 @@ class AnnounceController extends Controller
}
}
protected function generateFailedAnnounceResponse(TrackerException $trackerException): array
protected function generateFailedAnnounceResponse(TrackerException $trackerException): string
{
return [
'failure reason' => $trackerException->getMessage(),
'min interval' => self::MIN,
];
$message = $trackerException->getMessage();
return 'd14:failure reason'.\strlen($message).':'.$message.'8:intervali'.self::MIN.'e12:min intervali'.self::MIN.'ee';
}
/**
* Send Final Announce Response.
*/
protected function sendFinalAnnounceResponse($repDict): Response
protected function sendFinalAnnounceResponse(string $response): Response
{
return response(Bencode::bencode($repDict), headers: self::HEADERS);
return response($response, headers: self::HEADERS);
}
}