diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 198c2b8589..6c359212af 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1497,7 +1497,25 @@ function sslip(Server $server) } $ip = $server->ip === 'host.docker.internal' ? base_ip() : $server->ip; - return 'http://'.sslipHostLabel($ip).'.sslip.io'; + return 'http://'.sslipHostLabel(resolveHostToIp($ip)).'.sslip.io'; +} + +/** + * Resolve a hostname to its IPv4 (or else IPv6) address. sslip.io only answers for names that + * contain an IP address. Returns the input unchanged if it is already an IP or does not resolve. + */ +function resolveHostToIp(string $host): string +{ + if (filter_var(normalizeIpAddress($host), FILTER_VALIDATE_IP) !== false) { + return $host; + } + $ipv4 = gethostbyname($host); + if (filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { + return $ipv4; + } + $ipv6 = data_get(@dns_get_record($host, DNS_AAAA), '0.ipv6'); + + return is_string($ipv6) && $ipv6 !== '' ? $ipv6 : $host; } function service_templates_cache_key(): string diff --git a/tests/Feature/SslipServerHostnameTest.php b/tests/Feature/SslipServerHostnameTest.php new file mode 100644 index 0000000000..dca13318e7 --- /dev/null +++ b/tests/Feature/SslipServerHostnameTest.php @@ -0,0 +1,15 @@ + $address]); + $server->id = 1; + + expect(sslip($server))->toBe($expected); +})->with([ + 'IPv4' => ['188.245.12.182', 'http://188.245.12.182.sslip.io'], + 'IPv6' => ['2a01:4f8:c016:bd23::1', 'http://2a01-4f8-c016-bd23--1.sslip.io'], + 'resolvable hostname' => ['localhost', 'http://127.0.0.1.sslip.io'], + 'unresolvable hostname' => ['coolify-test.invalid', 'http://coolify-test.invalid.sslip.io'], +]);