Skip to content

Commit c6bd7ff

Browse files
committed
Improve logic handle auto-allocation of ports for a server
1 parent 7638ffc commit c6bd7ff

File tree

10 files changed

+201
-79
lines changed

10 files changed

+201
-79
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Allocation;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class AutoAllocationNotEnabledException extends DisplayException
8+
{
9+
/**
10+
* AutoAllocationNotEnabledException constructor.
11+
*/
12+
public function __construct()
13+
{
14+
parent::__construct(
15+
'Server auto-allocation is not enabled for this instance.'
16+
);
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Allocation;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class NoAutoAllocationSpaceAvailableException extends DisplayException
8+
{
9+
/**
10+
* NoAutoAllocationSpaceAvailableException constructor.
11+
*/
12+
public function __construct()
13+
{
14+
parent::__construct(
15+
'Cannot assign additional allocation: no more space available on node.'
16+
);
17+
}
18+
}

app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php

Lines changed: 10 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
44

5-
use Illuminate\Contracts\Config\Repository;
65
use Pterodactyl\Models\Server;
76
use Illuminate\Http\JsonResponse;
87
use Pterodactyl\Models\Allocation;
@@ -11,13 +10,12 @@
1110
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
1211
use Pterodactyl\Transformers\Api\Client\AllocationTransformer;
1312
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
13+
use Pterodactyl\Services\Allocations\FindAssignableAllocationService;
1414
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest;
15+
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest;
1516
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest;
1617
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest;
17-
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest;
1818
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest;
19-
use Pterodactyl\Services\Allocations\AssignmentService;
20-
use Illuminate\Support\Facades\Log;
2119

2220
class NetworkAllocationController extends ClientApiController
2321
{
@@ -32,37 +30,27 @@ class NetworkAllocationController extends ClientApiController
3230
private $serverRepository;
3331

3432
/**
35-
* @var \Pterodactyl\Services\Allocations\AssignmentService
33+
* @var \Pterodactyl\Services\Allocations\FindAssignableAllocationService
3634
*/
37-
protected $assignmentService;
35+
private $assignableAllocationService;
3836

3937
/**
4038
* NetworkController constructor.
4139
*
4240
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository
4341
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
44-
* @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService
45-
* @param \Illuminate\Contracts\Config\Repository $config
46-
*/
47-
48-
/**
49-
* @var \Illuminate\Contracts\Config\Repository
42+
* @param \Pterodactyl\Services\Allocations\FindAssignableAllocationService $assignableAllocationService
5043
*/
51-
private $config;
52-
5344
public function __construct(
5445
AllocationRepository $repository,
5546
ServerRepository $serverRepository,
56-
AssignmentService $assignmentService,
57-
Repository $config
58-
47+
FindAssignableAllocationService $assignableAllocationService
5948
) {
6049
parent::__construct();
6150

6251
$this->repository = $repository;
6352
$this->serverRepository = $serverRepository;
64-
$this->assignmentService = $assignmentService;
65-
$this->config = $config;
53+
$this->assignableAllocationService = $assignableAllocationService;
6654
}
6755

6856
/**
@@ -125,52 +113,16 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server,
125113
/**
126114
* Set the notes for the allocation for a server.
127115
*s
116+
*
128117
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest $request
129118
* @param \Pterodactyl\Models\Server $server
130-
* @param \Pterodactyl\Models\Allocation $allocation
131119
* @return array
132120
*
133121
* @throws \Pterodactyl\Exceptions\DisplayException
134-
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
135-
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
136122
*/
137-
public function addNew(NewAllocationRequest $request, Server $server): array
123+
public function store(NewAllocationRequest $request, Server $server): array
138124
{
139-
$topRange = config('pterodactyl.allocation.stop',0);
140-
$bottomRange = config('pterodactyl.allocation.start',0);
141-
142-
if($server->allocation_limit <= $server->allocations->count()) {
143-
throw new DisplayException(
144-
'You have created the maximum number of allocations!'
145-
);
146-
}
147-
148-
$allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->whereNull('server_id')->first();
149-
150-
if(!$allocation) {
151-
if($server->node->allocations()->where('ip',$server->allocation->ip)->where([['port', '>=', $bottomRange ], ['port', '<=', $topRange],])->count() >= $topRange-$bottomRange+1 || !config('allocation.enabled', false)) {
152-
throw new DisplayException(
153-
'No more allocations available!'
154-
);
155-
}
156-
157-
$allPorts = $server->node->allocations()->select(['port'])->where('ip',$server->allocation->ip)->get()->pluck('port')->toArray();
158-
159-
do {
160-
$port = rand($bottomRange, $topRange);
161-
} while(array_search($port, $allPorts));
162-
163-
$this->assignmentService->handle($server->node,[
164-
'allocation_ip'=>$server->allocation->ip,
165-
'allocation_ports'=>[$port],
166-
'server_id'=>$server->id
167-
]);
168-
169-
$allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->where('port', $port)->first();
170-
171-
}
172-
173-
$allocation->update(['server_id' => $server->id]);
125+
$allocation = $this->assignableAllocationService->handle($server);
174126

175127
return $this->fractal->item($allocation)
176128
->transformWith($this->getTransformer(AllocationTransformer::class))

app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public function rules()
2121
'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60',
2222
'pterodactyl:console:count' => 'required|integer|min:1',
2323
'pterodactyl:console:frequency' => 'required|integer|min:10',
24-
'allocation:enabled' => 'required|in:true,false',
25-
'pterodactyl:allocation:start' => 'required|integer|between:2000,65535',
26-
'pterodactyl:allocation:stop' => 'required|integer|between:2000,65535',
24+
'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false',
25+
'pterodactyl:client_features:allocations:range_start' => 'required|integer|between:1024,65535',
26+
'pterodactyl:client_features:allocations:range_end' => 'required|integer|between:1024,65535',
2727
];
2828
}
2929

@@ -40,9 +40,9 @@ public function attributes()
4040
'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout',
4141
'pterodactyl:console:count' => 'Console Message Count',
4242
'pterodactyl:console:frequency' => 'Console Frequency Tick',
43-
'allocation:enabled' => 'Auto Create Allocations Enabled',
44-
'pterodactyl:allocation:start' => 'Starting Port',
45-
'pterodactyl:allocation:stop' => 'Ending Port',
43+
'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled',
44+
'pterodactyl:client_features:allocations:range_start' => 'Starting Port',
45+
'pterodactyl:client_features:allocations:range_end' => 'Ending Port',
4646
];
4747
}
4848
}

app/Providers/SettingsServiceProvider.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class SettingsServiceProvider extends ServiceProvider
3030
'pterodactyl:console:count',
3131
'pterodactyl:console:frequency',
3232
'pterodactyl:auth:2fa_required',
33-
'allocation:enabled',
34-
'pterodactyl:allocation:stop',
35-
'pterodactyl:allocation:start',
33+
'pterodactyl:client_features:allocations:enabled',
34+
'pterodactyl:client_features:allocations:range_start',
35+
'pterodactyl:client_features:allocations:range_end',
3636
];
3737

3838
/**
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
namespace Pterodactyl\Services\Allocations;
4+
5+
use Webmozart\Assert\Assert;
6+
use Pterodactyl\Models\Server;
7+
use Pterodactyl\Models\Allocation;
8+
use Pterodactyl\Exceptions\DisplayException;
9+
use Pterodactyl\Exceptions\Service\Allocation\AutoAllocationNotEnabledException;
10+
use Pterodactyl\Exceptions\Service\Allocation\NoAutoAllocationSpaceAvailableException;
11+
12+
class FindAssignableAllocationService
13+
{
14+
/**
15+
* @var \Pterodactyl\Services\Allocations\AssignmentService
16+
*/
17+
private $service;
18+
19+
/**
20+
* FindAssignableAllocationService constructor.
21+
*
22+
* @param \Pterodactyl\Services\Allocations\AssignmentService $service
23+
*/
24+
public function __construct(AssignmentService $service)
25+
{
26+
$this->service = $service;
27+
}
28+
29+
/**
30+
* Finds an existing unassigned allocation and attempts to assign it to the given server. If
31+
* no allocation can be found, a new one will be created with a random port between the defined
32+
* range from the configuration.
33+
*
34+
* @param \Pterodactyl\Models\Server $server
35+
* @return \Pterodactyl\Models\Allocation
36+
*
37+
* @throws \Pterodactyl\Exceptions\DisplayException
38+
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
39+
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
40+
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
41+
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
42+
*/
43+
public function handle(Server $server)
44+
{
45+
if (!config('pterodactyl.client_features.allocations.enabled')) {
46+
throw new AutoAllocationNotEnabledException;
47+
}
48+
49+
if ($server->allocations()->count() >= $server->allocation_limit) {
50+
throw new DisplayException(
51+
'Cannot assign additional allocations to this server: limit has been reached.'
52+
);
53+
}
54+
55+
// Attempt to find a given available allocation for a server. If one cannot be found
56+
// we will fall back to attempting to create a new allocation that can be used for the
57+
// server.
58+
/** @var \Pterodactyl\Models\Allocation|null $allocation */
59+
$allocation = $server->node->allocations()
60+
->where('ip', $server->allocation->ip)
61+
->whereNull('server_id')
62+
->inRandomOrder()
63+
->first();
64+
65+
$allocation = $allocation ?? $this->createNewAllocation($server);
66+
67+
$allocation->update(['server_id' => $server->id]);
68+
69+
return $allocation->refresh();
70+
}
71+
72+
/**
73+
* @param \Pterodactyl\Models\Server $server
74+
* @return \Pterodactyl\Models\Allocation
75+
*
76+
* @throws \Pterodactyl\Exceptions\DisplayException
77+
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
78+
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
79+
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
80+
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
81+
*/
82+
protected function createNewAllocation(Server $server): Allocation
83+
{
84+
$start = config('pterodactyl.client_features.allocations.range_start', null);
85+
$end = config('pterodactyl.client_features.allocations.range_end', null);
86+
87+
if (!$start || !$end) {
88+
throw new NoAutoAllocationSpaceAvailableException;
89+
}
90+
91+
Assert::integerish($start);
92+
Assert::integerish($end);
93+
94+
// Get all of the currently allocated ports for the node so that we can figure out
95+
// which port might be available.
96+
$ports = $server->node->allocations()
97+
->where('ip', $server->allocation->ip)
98+
->whereBetween('port', [$start, $end])
99+
->pluck('port');
100+
101+
// Compute the difference of the range and the currently created ports, finding
102+
// any port that does not already exist in the database. We will then use this
103+
// array of ports to create a new allocation to assign to the server.
104+
$available = array_diff(range($start, $end), $ports->toArray());
105+
106+
// If we've already allocated all of the ports, just abort.
107+
if (empty($available)) {
108+
throw new NoAutoAllocationSpaceAvailableException;
109+
}
110+
111+
// dd($available, array_rand($available));
112+
// Pick a random port out of the remaining available ports.
113+
/** @var int $port */
114+
$port = $available[array_rand($available)];
115+
116+
$this->service->handle($server->node, [
117+
'server_id' => $server->id,
118+
'allocation_ip' => $server->allocation->ip,
119+
'allocation_ports' => [$port],
120+
]);
121+
122+
/** @var \Pterodactyl\Models\Allocation $allocation */
123+
$allocation = $server->node->allocations()
124+
->where('ip', $server->allocation->ip)
125+
->where('port', $port)
126+
->firstOrFail();
127+
128+
return $allocation;
129+
}
130+
}

config/pterodactyl.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@
143143
// The total number of tasks that can exist for any given schedule at once.
144144
'per_schedule_task_limit' => 10,
145145
],
146+
147+
'allocations' => [
148+
'enabled' => env('PTERODACTYL_CLIENT_ALLOCATIONS_ENABLED', false),
149+
'range_start' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_START'),
150+
'range_end' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_END'),
151+
],
146152
],
147153

148154
/*

resources/scripts/api/server/network/createServerAllocation.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import { Allocation } from '@/api/server/getServer';
22
import http from '@/api/http';
33
import { rawDataToServerAllocation } from '@/api/transformers';
44

5-
export default (uuid: string): Promise<Allocation> => {
6-
return new Promise((resolve, reject) => {
7-
http.get(`/api/client/servers/${uuid}/network/allocations/new`)
8-
.then(({ data }) => resolve(rawDataToServerAllocation(data)))
9-
.catch(reject);
10-
});
5+
export default async (uuid: string): Promise<Allocation> => {
6+
const { data } = await http.post(`/api/client/servers/${uuid}/network/allocations`);
7+
8+
return rawDataToServerAllocation(data);
119
};

resources/views/admin/settings/advanced.blade.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,24 +114,24 @@
114114
<div class="form-group col-md-4">
115115
<label class="control-label">Status</label>
116116
<div>
117-
<select class="form-control" name="allocation:enabled">
117+
<select class="form-control" name="pterodactyl:client_features:allocations:enabled">
118118
<option value="false">Disabled</option>
119-
<option value="true" @if(old('allocation:enabled', config('allocation.enabled'))) selected @endif>Enabled</option>
119+
<option value="true" @if(old('pterodactyl:client_features:allocations:enabled', config('pterodactyl.client_features.allocations.enabled'))) selected @endif>Enabled</option>
120120
</select>
121-
<p class="text-muted small">If enabled, the panel will attempt to auto create a new allocation in the range specified if there are no more allocations already created on the node.</p>
121+
<p class="text-muted small">If enabled users will have the option to automatically create new allocations for their server via the frontend.</p>
122122
</div>
123123
</div>
124124
<div class="form-group col-md-4">
125125
<label class="control-label">Starting Port</label>
126126
<div>
127-
<input type="number" required class="form-control" name="pterodactyl:allocation:start" value="{{ old('pterodactyl:allocation:start', config('pterodactyl.allocation.start')) }}">
127+
<input type="number" required class="form-control" name="pterodactyl:client_features:allocations:range_start" value="{{ old('pterodactyl:client_features:allocations:range_start', config('pterodactyl.client_features.allocations.range_start')) }}">
128128
<p class="text-muted small">The starting port in the range that can be automatically allocated.</p>
129129
</div>
130130
</div>
131131
<div class="form-group col-md-4">
132132
<label class="control-label">Ending Port</label>
133133
<div>
134-
<input type="number" required class="form-control" name="pterodactyl:allocation:stop" value="{{ old('pterodactyl:allocation:stop', config('pterodactyl.allocation.stop')) }}">
134+
<input type="number" required class="form-control" name="pterodactyl:client_features:allocations:range_end" value="{{ old('pterodactyl:client_features:allocations:range_end', config('pterodactyl.client_features.allocations.range_end')) }}">
135135
<p class="text-muted small">The ending port in the range that can be automatically allocated.</p>
136136
</div>
137137
</div>

routes/api-client.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@
8282

8383
Route::group(['prefix' => '/network', 'middleware' => [AllocationBelongsToServer::class]], function () {
8484
Route::get('/allocations', 'Servers\NetworkAllocationController@index');
85+
Route::post('/allocations', 'Servers\NetworkAllocationController@store');
8586
Route::post('/allocations/{allocation}', 'Servers\NetworkAllocationController@update');
8687
Route::post('/allocations/{allocation}/primary', 'Servers\NetworkAllocationController@setPrimary');
87-
Route::get('/allocations/new', 'Servers\NetworkAllocationController@addNew');
8888
Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete');
8989
});
9090

0 commit comments

Comments
 (0)