diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index 1f71465a1a..8622b9be0c 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -2,6 +2,7 @@ namespace App\Actions\Database; +use App\Models\Server; use App\Models\ServiceDatabase; use App\Models\StandaloneClickhouse; use App\Models\StandaloneDragonfly; @@ -58,6 +59,7 @@ class StartDatabaseProxy $configuration_dir = database_proxy_dir($database->uuid); $host_configuration_dir = devHostDockerPath($server, $configuration_dir); $timeoutConfig = $this->buildProxyTimeoutConfig($database->public_port_timeout); + $listenConfig = $this->buildListenConfig($database->public_port, $this->isNetworkIpv6Enabled($network, $server)); $nginxconf = <<public_port; + $listenConfig proxy_pass $containerName:$internalPort; $timeoutConfig } @@ -164,6 +166,27 @@ class StartDatabaseProxy return false; } + /** + * Docker publishes the port on [::] too and forwards it to the container's IPv6 address + * when the network has IPv6 enabled, so nginx must listen there as well. + */ + private function isNetworkIpv6Enabled(string $network, Server $server): bool + { + $safeNetwork = escapeshellarg($network); + $output = instant_remote_process(["docker network inspect {$safeNetwork} --format '{{.EnableIPv6}}'"], $server, false); + + return trim((string) $output) === 'true'; + } + + private function buildListenConfig(int $port, bool $ipv6Enabled): string + { + if (! $ipv6Enabled) { + return "listen {$port};"; + } + + return "listen {$port};\n listen [::]:{$port};"; + } + private function buildProxyTimeoutConfig(?int $timeout): string { if ($timeout === null || $timeout < 1) { diff --git a/tests/Feature/Service/StartDatabaseProxyTest.php b/tests/Feature/Service/StartDatabaseProxyTest.php index b564040a2d..6dda6f3f40 100644 --- a/tests/Feature/Service/StartDatabaseProxyTest.php +++ b/tests/Feature/Service/StartDatabaseProxyTest.php @@ -70,3 +70,15 @@ test('buildProxyTimeoutConfig normalizes invalid values to default', function (? [-10, 'proxy_timeout 3600s;'], [120, 'proxy_timeout 120s;'], ]); + +test('buildListenConfig adds an IPv6 listener only when the network has IPv6 enabled', function (bool $ipv6Enabled, array $expectedListeners) { + $action = new StartDatabaseProxy; + $method = new ReflectionMethod($action, 'buildListenConfig'); + + $listeners = array_map('trim', explode("\n", $method->invoke($action, 28197, $ipv6Enabled))); + + expect($listeners)->toBe($expectedListeners); +})->with([ + 'IPv4 only network' => [false, ['listen 28197;']], + 'IPv6 enabled network' => [true, ['listen 28197;', 'listen [::]:28197;']], +]);