fix(engine): parse WireGuard CIDR for address allocation (any prefix length)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 10:48:16 +02:00
parent b65fe69088
commit 8a392bfd06
2 changed files with 30 additions and 6 deletions

View File

@ -4,7 +4,6 @@ namespace App\Services\Wireguard;
use App\Models\Host;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
use RuntimeException;
/**
@ -17,12 +16,19 @@ class LocalWireguardHub implements WireguardHub
{
public function allocateIp(): string
{
$used = Host::query()->whereNotNull('wg_ip')->pluck('wg_ip')->all();
$prefix = Str::beforeLast(config('provisioning.wireguard.subnet', '10.66.0.0/24'), '.');
$used = array_flip(Host::query()->whereNotNull('wg_ip')->pluck('wg_ip')->all());
$hubIp = (string) config('provisioning.wireguard.hub_ip');
for ($octet = 2; $octet <= 254; $octet++) {
$ip = "{$prefix}.{$octet}";
if (! in_array($ip, $used, true)) {
[$network, $bits] = explode('/', config('provisioning.wireguard.subnet', '10.66.0.0/24'));
$bits = (int) $bits;
$hostBits = 32 - $bits;
$size = 2 ** $hostBits;
$base = ip2long($network) & (0xFFFFFFFF << $hostBits) & 0xFFFFFFFF;
// Skip the network address (offset 0) and broadcast (offset size-1).
for ($offset = 1; $offset < $size - 1; $offset++) {
$ip = long2ip($base + $offset);
if ($ip !== $hubIp && ! isset($used[$ip])) {
return $ip;
}
}

View File

@ -5,6 +5,7 @@ use App\Services\Proxmox\FakeProxmoxClient;
use App\Services\Ssh\CommandResult;
use App\Services\Ssh\FakeRemoteShell;
use App\Services\Wireguard\FakeWireguardHub;
use App\Services\Wireguard\LocalWireguardHub;
it('scripts remote command output and records calls (FakeRemoteShell)', function () {
$shell = new FakeRemoteShell;
@ -34,6 +35,23 @@ it('allocates ips and tracks peers (FakeWireguardHub)', function () {
expect($hub->peers())->toBe([]);
});
it('allocates addresses within a /24 subnet, skipping hub and used (LocalWireguardHub)', function () {
config()->set('provisioning.wireguard.subnet', '10.66.0.0/24');
config()->set('provisioning.wireguard.hub_ip', '10.66.0.1');
Host::factory()->create(['wg_ip' => '10.66.0.2']);
// .0 network, .1 hub, .2 used → first free is .3
expect((new LocalWireguardHub)->allocateIp())->toBe('10.66.0.3');
});
it('respects a non-/24 subnet (LocalWireguardHub)', function () {
config()->set('provisioning.wireguard.subnet', '10.66.5.128/25');
config()->set('provisioning.wireguard.hub_ip', '10.66.5.129');
// network .128, hub .129 → first free is .130
expect((new LocalWireguardHub)->allocateIp())->toBe('10.66.5.130');
});
it('reports node capacity for a host (FakeProxmoxClient)', function () {
$host = Host::factory()->create(['wg_ip' => '10.66.0.5']);
$client = (new FakeProxmoxClient)->forHost($host);