update: working optimized announce

This commit is contained in:
HDVinnie
2023-01-17 22:01:28 -05:00
parent bf9b72c47c
commit b044cbc92c
9 changed files with 71 additions and 98 deletions

View File

@@ -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')
)

View File

@@ -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);
}
}
}

View File

@@ -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()

View File

@@ -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();

View File

@@ -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(

View File

@@ -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);

View File

@@ -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.']';

View File

@@ -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(),

View File

@@ -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');
}
};