Path: blob/1.0-develop/app/Services/Allocations/AssignmentService.php
14042 views
<?php12namespace Pterodactyl\Services\Allocations;34use IPTools\Network;5use Pterodactyl\Models\Node;6use Illuminate\Database\ConnectionInterface;7use Pterodactyl\Exceptions\DisplayException;8use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;9use Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException;10use Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException;11use Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException;12use Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException;1314class AssignmentService15{16public const CIDR_MAX_BITS = 25;17public const CIDR_MIN_BITS = 32;18public const PORT_FLOOR = 1024;19public const PORT_CEIL = 65535;20public const PORT_RANGE_LIMIT = 1000;21public const PORT_RANGE_REGEX = '/^(\d{4,5})-(\d{4,5})$/';2223/**24* AssignmentService constructor.25*/26public function __construct(protected AllocationRepositoryInterface $repository, protected ConnectionInterface $connection)27{28}2930/**31* Insert allocations into the database and link them to a specific node.32*33* @throws DisplayException34* @throws CidrOutOfRangeException35* @throws InvalidPortMappingException36* @throws PortOutOfRangeException37* @throws TooManyPortsInRangeException38*/39public function handle(Node $node, array $data): void40{41$explode = explode('/', $data['allocation_ip']);42if (count($explode) !== 1) {43if (!ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) {44throw new CidrOutOfRangeException();45}46}4748try {49// TODO: how should we approach supporting IPv6 with this?50// gethostbyname only supports IPv4, but the alternative (dns_get_record) returns51// an array of records, which is not ideal for this use case, we need a SINGLE52// IP to use, not multiple.53$underlying = gethostbyname($data['allocation_ip']);54$parsed = Network::parse($underlying);55} catch (\Exception $exception) {56// @phpstan-ignore-next-line variable.undefined57throw new DisplayException("Could not parse provided allocation IP address ({$underlying}): {$exception->getMessage()}", $exception);58}5960$this->connection->beginTransaction();61foreach ($parsed as $ip) {62foreach ($data['allocation_ports'] as $port) {63if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) {64throw new InvalidPortMappingException($port);65}6667$insertData = [];68if (preg_match(self::PORT_RANGE_REGEX, $port, $matches)) {69$block = range($matches[1], $matches[2]);7071if (count($block) > self::PORT_RANGE_LIMIT) {72throw new TooManyPortsInRangeException();73}7475if ((int) $matches[1] <= self::PORT_FLOOR || (int) $matches[2] > self::PORT_CEIL) {76throw new PortOutOfRangeException();77}7879foreach ($block as $unit) {80$insertData[] = [81'node_id' => $node->id,82'ip' => $ip->__toString(),83'port' => (int) $unit,84'ip_alias' => array_get($data, 'allocation_alias'),85'server_id' => null,86];87}88} else {89if ((int) $port <= self::PORT_FLOOR || (int) $port > self::PORT_CEIL) {90throw new PortOutOfRangeException();91}9293$insertData[] = [94'node_id' => $node->id,95'ip' => $ip->__toString(),96'port' => (int) $port,97'ip_alias' => array_get($data, 'allocation_alias'),98'server_id' => null,99];100}101102$this->repository->insertIgnore($insertData);103}104}105106$this->connection->commit();107}108}109110111