From b044cbc92ca94652dd7f249ce61d9930746f13bb Mon Sep 17 00:00:00 2001 From: HDVinnie Date: Tue, 17 Jan 2023 22:01:28 -0500 Subject: [PATCH] update: working optimized announce --- app/Console/Commands/AutoHighspeedTag.php | 2 +- app/Http/Controllers/AnnounceController.php | 116 ++++++++---------- .../Controllers/TorrentPeerController.php | 2 - app/Http/Controllers/User/UserController.php | 2 +- app/Http/Livewire/UserActive.php | 2 +- app/Jobs/ProcessAnnounce.php | 33 +++-- app/Models/Peer.php | 2 +- database/factories/PeerFactory.php | 4 +- .../2022_12_22_004317_update_peers_table.php | 6 +- 9 files changed, 71 insertions(+), 98 deletions(-) diff --git a/app/Console/Commands/AutoHighspeedTag.php b/app/Console/Commands/AutoHighspeedTag.php index 22c9c7f2e..cc4116410 100644 --- a/app/Console/Commands/AutoHighspeedTag.php +++ b/app/Console/Commands/AutoHighspeedTag.php @@ -49,7 +49,7 @@ class AutoHighspeedTag extends Command ->leftJoinSub( Peer::distinct() ->select('torrent_id') - ->whereRaw("INET6_NTOA(ip) IN ('".$seedboxIps->implode("','")."')"), + ->whereRaw("ip IN ('".$seedboxIps->implode("','")."')"), 'highspeed_torrents', fn ($join) => $join->on('torrents.id', '=', 'highspeed_torrents.torrent_id') ) diff --git a/app/Http/Controllers/AnnounceController.php b/app/Http/Controllers/AnnounceController.php index 5e80e97ea..709ab1f20 100644 --- a/app/Http/Controllers/AnnounceController.php +++ b/app/Http/Controllers/AnnounceController.php @@ -21,7 +21,7 @@ use App\Exceptions\TrackerException; use App\Helpers\Bencode; use App\Jobs\ProcessAnnounce; use App\Models\BlacklistClient; -use App\Models\Group; +use App\Models\Peer; use App\Models\Torrent; use App\Models\User; use Illuminate\Http\Request; @@ -99,7 +99,7 @@ class AnnounceController extends Controller $queries = $this->checkAnnounceFields($request); // Check user via supplied passkey. - [$user, $group] = $this->checkUser($passkey, $queries); + $user = $this->checkUser($passkey, $queries); // Get Torrent Info Array from queries and judge if user can reach it. $torrent = $this->checkTorrent($queries['info_hash']); @@ -117,14 +117,14 @@ class AnnounceController extends Controller // Check Download Slots. if (\config('announce.slots_system.enabled')) { - $this->checkDownloadSlots($queries, $user, $group); + $this->checkDownloadSlots($queries, $user); } // Generate A Response For The Torrent Client. $repDict = $this->generateSuccessAnnounceResponse($queries, $torrent, $user); // Process Annnounce Job. - $this->processAnnounceJob($queries, $user, $torrent, $group); + $this->processAnnounceJob($queries, $user, $torrent); } catch (TrackerException $exception) { $repDict = $this->generateFailedAnnounceResponse($exception); } finally { @@ -169,9 +169,8 @@ class AnnounceController extends Controller (string) $userAgent ), new TrackerException(121)); - $clientBlacklist = \cache()->rememberForever('client_blacklist', fn () => BlacklistClient::all()->pluck('name')->toArray()); - // Block Blacklisted Clients + $clientBlacklist = \cache()->rememberForever('client_blacklist', fn () => BlacklistClient::all()->pluck('name')->toArray()); \throw_if( \in_array($userAgent, $clientBlacklist), new TrackerException(128, [':ua' => $request->header('User-Agent')]) @@ -184,7 +183,7 @@ class AnnounceController extends Controller * @throws \App\Exceptions\TrackerException * @throws \Throwable */ - protected function checkPasskey(string $passkey): void + protected function checkPasskey($passkey): void { // If Passkey Is Not Provided Return Error to Client \throw_if($passkey === null, new TrackerException(130, [':attribute' => 'passkey'])); @@ -279,7 +278,7 @@ class AnnounceController extends Controller ), new TrackerException(135, [':port' => $queries['port']])); // Part.4 Get User Ip Address - $queries['ip-address'] = \inet_pton($request->getClientIp()); + $queries['ip-address'] = $request->getClientIp(); // Part.5 Get Users Agent $queries['user-agent'] = $request->headers->get('user-agent'); @@ -287,6 +286,9 @@ class AnnounceController extends Controller // Part.6 bin2hex info_hash $queries['info_hash'] = \bin2hex($queries['info_hash']); + // Part.7 bin2hex peer_id + $queries['peer_id'] = \bin2hex($queries['peer_id']); + return $queries; } @@ -296,57 +298,48 @@ class AnnounceController extends Controller * @throws \App\Exceptions\TrackerException * @throws \Throwable */ - protected function checkUser(string $passkey, array $queries): array + protected function checkUser($passkey, $queries): object { - // Cached System Required Groups - $deniedGroups = \cache()->rememberForever( - 'denied_groups', - fn () => Group::query() + $deniedGroups = \cache()->remember('denied_groups', 300, function () { + return DB::table('groups') ->selectRaw("min(case when slug = 'banned' then id end) as banned_id") ->selectRaw("min(case when slug = 'validating' then id end) as validating_id") ->selectRaw("min(case when slug = 'disabled' then id end) as disabled_id") - ->first() - ); + ->first(); + }); // Check Passkey Against Users Table - $user = \cache()->rememberForever('user:'.$passkey, fn () => User::query() - ->select(['id', 'group_id', 'can_download']) + $user = User::with('group') + ->select(['id', 'group_id', 'can_download', 'uploaded', 'downloaded']) ->where('passkey', '=', $passkey) - ->first()); - - $group = \cache()->rememberForever('group:'.$user->group_id, fn () => Group::query() - ->select(['id', 'download_slots', 'is_immune', 'is_freeleech', 'is_double_upload']) - ->where('id', '=', $user->group_id) - ->first()); + ->first(); // If User Doesn't Exist Return Error to Client - \throw_if($user === null, new TrackerException(140)); + \throw_if($user === null, + new TrackerException(140) + ); // If User Account Is Unactivated/Validating Return Error to Client - \throw_if( - $user->group_id === $deniedGroups->validating_id, + \throw_if($user->group_id === $deniedGroups->validating_id, new TrackerException(141, [':status' => 'Unactivated/Validating']) ); // If User Download Rights Are Disabled Return Error to Client - \throw_if( - $user->can_download === 0 && $queries['left'] !== '0', + \throw_if($user->can_download === 0 && $queries['left'] !== '0', new TrackerException(142) ); // If User Is Banned Return Error to Client - \throw_if( - $user->group_id === $deniedGroups->banned_id, + \throw_if($user->group_id === $deniedGroups->banned_id, new TrackerException(141, [':status' => 'Banned']) ); // If User Is Disabled Return Error to Client - throw_if( - $user->group_id === $deniedGroups->disabled_id, + throw_if($user->group_id === $deniedGroups->disabled_id, new TrackerException(141, [':status' => 'Disabled']) ); - return [$user, $group]; + return $user; } /** @@ -355,16 +348,12 @@ class AnnounceController extends Controller * @throws \App\Exceptions\TrackerException * @throws \Throwable */ - protected function checkTorrent(string $infoHash): Torrent + protected function checkTorrent($infoHash): object { // Check Info Hash Against Torrents Table - $torrent = Torrent::withAnyStatus() - ->with([ - 'peers' => fn ($query) => $query - ->select(['id', 'torrent_id', 'peer_id', 'user_id', 'left', 'seeder', 'port']) - ->selectRaw('INET6_NTOA(ip) as ip') - ]) + $torrent = Torrent::with('peers') ->select(['id', 'free', 'doubleup', 'seeders', 'leechers', 'times_completed', 'status']) + ->withAnyStatus() ->where('info_hash', '=', $infoHash) ->first(); @@ -372,20 +361,17 @@ class AnnounceController extends Controller \throw_if($torrent === null, new TrackerException(150)); // If Torrent Is Pending Moderation Return Error to Client - \throw_if( - $torrent->status === self::PENDING, + \throw_if($torrent->status === self::PENDING, new TrackerException(151, [':status' => 'PENDING In Moderation']) ); // If Torrent Is Rejected Return Error to Client - \throw_if( - $torrent->status === self::REJECTED, + \throw_if($torrent->status === self::REJECTED, new TrackerException(151, [':status' => 'REJECTED In Moderation']) ); // If Torrent Is Postponed Return Error to Client - \throw_if( - $torrent->status === self::POSTPONED, + \throw_if($torrent->status === self::POSTPONED, new TrackerException(151, [':status' => 'POSTPONED In Moderation']) ); @@ -398,10 +384,9 @@ class AnnounceController extends Controller * @throws \App\Exceptions\TrackerException * @throws \Throwable */ - private function checkPeer(Torrent $torrent, array $queries, User $user): void + private function checkPeer($torrent, $queries, $user): void { - \throw_if( - \strtolower($queries['event']) === 'completed' + \throw_if(\strtolower($queries['event']) === 'completed' && $torrent->peers ->where('peer_id', $queries['peer_id']) ->where('user_id', '=', $user->id) @@ -417,7 +402,7 @@ class AnnounceController extends Controller * @throws \Exception * @throws \Throwable */ - private function checkMinInterval(Torrent $torrent, array $queries, User $user): void + private function checkMinInterval($torrent, $queries, $user): void { $prevAnnounce = $torrent->peers ->where('peer_id', '=', $queries['peer_id']) @@ -425,8 +410,7 @@ class AnnounceController extends Controller ->first(); $setMin = \config('announce.min_interval.interval') ?? self::MIN; $randomMinInterval = \random_int($setMin, $setMin * 2); - \throw_if( - $prevAnnounce && $prevAnnounce->updated_at->greaterThan(\now()->subSeconds($randomMinInterval)) + \throw_if($prevAnnounce && $prevAnnounce->updated_at->greaterThan(\now()->subSeconds($randomMinInterval)) && \strtolower($queries['event']) !== 'completed' && \strtolower($queries['event']) !== 'stopped', new TrackerException(162, [':min' => $randomMinInterval]) ); @@ -438,7 +422,7 @@ class AnnounceController extends Controller * @throws \App\Exceptions\TrackerException * @throws \Throwable */ - private function checkMaxConnections(Torrent $torrent, User $user): void + private function checkMaxConnections($torrent, $user): void { // Pull Count On Users Peers Per Torrent For Rate Limiting $connections = $torrent->peers @@ -446,8 +430,7 @@ class AnnounceController extends Controller ->count(); // If Users Peer Count On A Single Torrent Is Greater Than X Return Error to Client - \throw_if( - $connections > \config('announce.rate_limit'), + \throw_if($connections > \config('announce.rate_limit'), new TrackerException(138, [':limit' => \config('announce.rate_limit')]) ); } @@ -458,19 +441,18 @@ class AnnounceController extends Controller * @throws \App\Exceptions\TrackerException * @throws \Throwable */ - private function checkDownloadSlots(array $queries, User $user, Group $group): void + private function checkDownloadSlots($queries, $user): void { - $max = $group->download_slots; + $max = $user->group->download_slots; if ($max !== null && $max >= 0 && $queries['left'] != 0) { - $count = DB::table('peers') + $count = Peer::query() ->where('user_id', '=', $user->id) ->where('peer_id', '!=', $queries['peer_id']) ->where('seeder', '=', 0) ->count(); - \throw_if( - $count >= $max, + \throw_if($count >= $max, new TrackerException(164, [':max' => $max]) ); } @@ -481,11 +463,11 @@ class AnnounceController extends Controller * * @throws \Exception */ - private function generateSuccessAnnounceResponse(array $queries, Torrent $torrent, User $user): array + private function generateSuccessAnnounceResponse($queries, $torrent, $user): array { // Build Response For Bittorrent Client $repDict = [ - 'interval' => random_int(self::MIN, self::MAX), + 'interval' => \random_int(self::MIN, self::MAX), 'min interval' => self::MIN, 'complete' => (int) $torrent->seeders, 'incomplete' => (int) $torrent->leechers, @@ -498,7 +480,7 @@ class AnnounceController extends Controller * We query peers from database and send peerlist, otherwise just quick return. */ if (\strtolower($queries['event']) !== 'stopped') { - $limit = (min($queries['numwant'], 25)); + $limit = (\min($queries['numwant'], 25)); // Get Torrents Peers (Only include leechers in a seeder's peerlist) $peers = $torrent->peers @@ -523,9 +505,9 @@ class AnnounceController extends Controller /** * Process Announce Database Queries. */ - private function processAnnounceJob(array $queries, User $user, Torrent $torrent, Group $group): void + private function processAnnounceJob($queries, $user, $torrent): void { - ProcessAnnounce::dispatch($queries, $user, $torrent, $group); + ProcessAnnounce::dispatch($queries, $user, $torrent); } protected function generateFailedAnnounceResponse(TrackerException $trackerException): array @@ -539,8 +521,8 @@ class AnnounceController extends Controller /** * Send Final Announce Response. */ - protected function sendFinalAnnounceResponse(array|null $repDict): Response + protected function sendFinalAnnounceResponse($repDict): Response { return response(Bencode::bencode($repDict), headers: self::HEADERS); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/TorrentPeerController.php b/app/Http/Controllers/TorrentPeerController.php index b203df092..7518dec93 100644 --- a/app/Http/Controllers/TorrentPeerController.php +++ b/app/Http/Controllers/TorrentPeerController.php @@ -26,8 +26,6 @@ class TorrentPeerController extends Controller $torrent = Torrent::withAnyStatus()->findOrFail($id); $peers = Peer::query() ->with(['user']) - ->select(['torrent_id', 'user_id', 'uploaded', 'downloaded', 'left', 'port', 'agent', 'created_at', 'updated_at', 'seeder']) - ->selectRaw('INET6_NTOA(ip) as ip') ->where('torrent_id', '=', $id) ->latest('seeder') ->get() diff --git a/app/Http/Controllers/User/UserController.php b/app/Http/Controllers/User/UserController.php index 83a2d9b6a..04fc13914 100644 --- a/app/Http/Controllers/User/UserController.php +++ b/app/Http/Controllers/User/UserController.php @@ -75,7 +75,7 @@ class UserController extends Controller $clients = $user->peers() ->select('agent', 'port') - ->selectRaw('INET6_NTOA(ip) as ip, MIN(created_at), MAX(updated_at), COUNT(*) as num_peers') + ->selectRaw('ip as ip, MIN(created_at), MAX(updated_at), COUNT(*) as num_peers') ->groupBy(['ip', 'port', 'agent']) ->get(); diff --git a/app/Http/Livewire/UserActive.php b/app/Http/Livewire/UserActive.php index 9a1fb0edd..c767c228a 100644 --- a/app/Http/Livewire/UserActive.php +++ b/app/Http/Livewire/UserActive.php @@ -80,6 +80,7 @@ class UserActive extends Component ->join('torrents', 'peers.torrent_id', '=', 'torrents.id') ->select( 'peers.id', + 'peers.ip', 'peers.port', 'peers.agent', 'peers.uploaded', @@ -96,7 +97,6 @@ class UserActive extends Component 'torrents.leechers', 'torrents.times_completed', ) - ->selectRaw('INET6_NTOA(ip) as ip') ->selectRaw('(1 - (peers.left / NULLIF(torrents.size, 0))) AS progress') ->where('peers.user_id', '=', $this->user->id) ->when( diff --git a/app/Jobs/ProcessAnnounce.php b/app/Jobs/ProcessAnnounce.php index a0437f6e8..d31d7e791 100644 --- a/app/Jobs/ProcessAnnounce.php +++ b/app/Jobs/ProcessAnnounce.php @@ -33,7 +33,7 @@ class ProcessAnnounce implements ShouldQueue /** * Create a new job instance. */ - public function __construct(protected $queries, protected $user, protected $torrent, protected $group) + public function __construct(protected $queries, protected $user, protected $torrent) { } @@ -111,7 +111,7 @@ class ProcessAnnounce implements ShouldQueue ); if ($personalFreeleech || - $this->group->is_freeleech == 1 || + $this->user->group->is_freeleech == 1 || $freeleechToken || \config('other.freeleech') == 1) { $modDownloaded = 0; @@ -125,7 +125,7 @@ class ProcessAnnounce implements ShouldQueue } if ($this->torrent->doubleup == 1 || - $this->group->is_double_upload == 1 || + $this->user->group->is_double_upload == 1 || \config('other.doubleup') == 1) { $modUploaded = $uploaded * 2; } else { @@ -159,7 +159,7 @@ class ProcessAnnounce implements ShouldQueue $history->active = 1; // Allow downgrading from `immune`, but never upgrade to it - $history->immune = (int) ($history->immune === null ? $this->group->is_immune : (bool) $history->immune && (bool) $this->group->is_immune); + $history->immune = (int) ($history->immune === null ? $this->user->group->is_immune : (bool) $history->immune && (bool) $this->user->group->is_immune); $history->save(); break; @@ -182,10 +182,9 @@ class ProcessAnnounce implements ShouldQueue // User Update if ($modUploaded > 0 || $modDownloaded > 0) { - $this->user->update([ - 'uploaded' => DB::raw('uploaded + '. (int) $modUploaded), - 'downloaded' => DB::raw('downloaded + '. (int) $modDownloaded), - ]); + $this->user->uploaded += $modUploaded; + $this->user->downloaded += $modDownloaded; + $this->user->save(); } // End User Update @@ -213,10 +212,9 @@ class ProcessAnnounce implements ShouldQueue // User Update if ($modUploaded > 0 || $modDownloaded > 0) { - $this->user->update([ - 'uploaded' => DB::raw('uploaded + '. (int) $modUploaded), - 'downloaded' => DB::raw('downloaded + '. (int) $modDownloaded), - ]); + $this->user->uploaded += $modUploaded; + $this->user->downloaded += $modDownloaded; + $this->user->save(); } // End User Update break; @@ -240,10 +238,9 @@ class ProcessAnnounce implements ShouldQueue // User Update if ($modUploaded > 0 || $modDownloaded > 0) { - $this->user->update([ - 'uploaded' => DB::raw('uploaded + '. (int) $modUploaded), - 'downloaded' => DB::raw('downloaded + '. (int) $modDownloaded), - ]); + $this->user->uploaded += $modUploaded; + $this->user->downloaded += $modDownloaded; + $this->user->save(); } // End User Update } @@ -252,13 +249,13 @@ class ProcessAnnounce implements ShouldQueue ->torrent ->peers ->where('left', '=', 0) - ->where('peer_id', '<>', $this->queries['peer_id']) + ->where('peer_id', '!=', $this->queries['peer_id']) ->count(); $otherLeechers = $this ->torrent ->peers ->where('left', '>', 0) - ->where('peer_id', '<>', $this->queries['peer_id']) + ->where('peer_id', '!=', $this->queries['peer_id']) ->count(); $this->torrent->seeders = $otherSeeders + (int) ($this->queries['left'] == 0); diff --git a/app/Models/Peer.php b/app/Models/Peer.php index 02f64d0d1..519b6543c 100644 --- a/app/Models/Peer.php +++ b/app/Models/Peer.php @@ -59,7 +59,7 @@ class Peer extends Model public function updateConnectableStateIfNeeded(): void { if (\config('announce.connectable_check')) { - $tmp_ip = inet_ntop(pack('A'.\strlen($this->ip), $this->ip)); + $tmp_ip = $this->ip; // IPv6 Check if (filter_var($tmp_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $tmp_ip = '['.$tmp_ip.']'; diff --git a/database/factories/PeerFactory.php b/database/factories/PeerFactory.php index c71a5755d..2d5761437 100644 --- a/database/factories/PeerFactory.php +++ b/database/factories/PeerFactory.php @@ -16,8 +16,8 @@ class PeerFactory extends Factory public function definition(): array { return [ - 'peer_id' => $this->faker->asciify('-qB4450-************'), - 'ip' => \inet_pton($this->faker->ipv4()), + 'peer_id' => $this->faker->randomNumber(), + 'ip' => $this->faker->ipv4(), 'port' => $this->faker->numberBetween(0, 65535), 'agent' => $this->faker->word(), 'uploaded' => $this->faker->randomNumber(), diff --git a/database/migrations/2022_12_22_004317_update_peers_table.php b/database/migrations/2022_12_22_004317_update_peers_table.php index 206511b67..232579bd0 100644 --- a/database/migrations/2022_12_22_004317_update_peers_table.php +++ b/database/migrations/2022_12_22_004317_update_peers_table.php @@ -3,6 +3,7 @@ use App\Models\Peer; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; return new class () extends Migration { @@ -13,8 +14,6 @@ return new class () extends Migration { */ public function up() { - Peer::truncate(); - Schema::disableForeignKeyConstraints(); Schema::table('peers', function (Blueprint $table) { @@ -30,8 +29,5 @@ return new class () extends Migration { }); Schema::enableForeignKeyConstraints(); - - DB::statement('ALTER TABLE `peers` MODIFY `peer_id` BINARY(20) NOT NULL'); - DB::statement('ALTER TABLE `peers` MODIFY `ip` VARBINARY(16) NOT NULL'); } };