From 7a2840fba693182133ac67fef175ae2513f3ddaf Mon Sep 17 00:00:00 2001 From: Taimoor Zaeem Date: Fri, 29 Aug 2025 20:18:16 +0500 Subject: [PATCH] fix: format of IPv6 address logged at PostgREST startup The IPv6 address logged at the startup like `::1:80` was wrong because the port isn't clearly separated. This commit corrects it, now logging as `[::1]:80`. This is done in accordance to RFC 3986. In short, we did this have a clear separation between the port and host because the components of an IPv6 are separated with the ':' character. Signed-off-by: Taimoor Zaeem --- CHANGELOG.md | 1 + src/PostgREST/Network.hs | 6 +++++- test/io/postgrest.py | 15 +++++++++++++-- test/io/test_io.py | 9 ++++++--- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7e9dc77..59102f8c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix logging the Haskell type instead of the listener error message directly by @laurenceisla in #3588 +- Fix format of `IPv6` address logged at PostgREST startup by @taimoorzaeem in #4291 ### Changed diff --git a/src/PostgREST/Network.hs b/src/PostgREST/Network.hs index f66afa587..c3107a6fd 100644 --- a/src/PostgREST/Network.hs +++ b/src/PostgREST/Network.hs @@ -13,5 +13,9 @@ resolveHost sock = do sn <- NS.getSocketName sock case sn of NS.SockAddrInet _ hostAddr -> pure $ Just $ fromString $ show $ fromHostAddress hostAddr - NS.SockAddrInet6 _ _ hostAddr6 _ -> pure $ Just $ fromString $ show $ fromHostAddress6 hostAddr6 + -- The IPv6 addresses are wrapped in [] brackets. This is done in accordance + -- to RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + -- In short, we did this to have a clear separation between the port and host + -- because the components of an IPv6 are separated with the ':' character. + NS.SockAddrInet6 _ _ hostAddr6 _ -> pure $ Just $ fromString $ "[" ++ show (fromHostAddress6 hostAddr6) ++ "]" _ -> pure Nothing diff --git a/test/io/postgrest.py b/test/io/postgrest.py index a75b249f9..5a7379290 100644 --- a/test/io/postgrest.py +++ b/test/io/postgrest.py @@ -97,7 +97,9 @@ def run( if port: env["PGRST_SERVER_PORT"] = str(port) env["PGRST_SERVER_HOST"] = host or "localhost" - baseurl = f"http://localhost:{port}" + # When constructing IPv6 address, host address should be bracketed like [host] + apihost = f"[{host}]" if host and is_ipv6(host) else "localhost" + baseurl = f"http://{apihost}:{port}" else: socketfile = pathlib.Path(tmpdir) / "postgrest.sock" env["PGRST_SERVER_UNIX_SOCKET"] = str(socketfile) @@ -105,7 +107,8 @@ def run( adminport = freeport(port) env["PGRST_ADMIN_SERVER_PORT"] = str(adminport) - adminurl = f"http://localhost:{adminport}" + adminhost = f"[{host}]" if host and is_ipv6(host) else "localhost" + adminurl = f"http://{adminhost}:{adminport}" command = [POSTGREST_BIN] env["HPCTIXFILE"] = hpctixfile() @@ -219,3 +222,11 @@ def sleep_pool_connection(url, seconds): session.get(url + f"/rpc/sleep?seconds={seconds}", timeout=0.1) except requests.exceptions.ReadTimeout: pass + + +def is_ipv6(addr): + try: + socket.inet_pton(socket.AF_INET6, addr) + return True + except OSError: + return False diff --git a/test/io/test_io.py b/test/io/test_io.py index 3c24c2256..fa6a9f85e 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -1362,9 +1362,9 @@ def test_log_postgrest_version(defaultenv): assert "Starting PostgREST %s..." % version in output[0] -def test_log_postgrest_host_and_port(defaultenv): +@pytest.mark.parametrize("host", ["127.0.0.1", "::1"]) +def test_log_postgrest_host_and_port(host, defaultenv): "PostgREST should output the host and port it is bound to." - host = "127.0.0.1" port = freeport() with run( @@ -1372,7 +1372,10 @@ def test_log_postgrest_host_and_port(defaultenv): ) as postgrest: output = postgrest.read_stdout(nlines=10) - assert f"API server listening on {host}:{port}" in output[2] # output-sensitive + if is_ipv6(host): # IPv6 + assert f"API server listening on [{host}]:{port}" in output[2] + else: # IPv4 + assert f"API server listening on {host}:{port}" in output[2] def test_succeed_w_role_having_superuser_settings(defaultenv):