From bf0a1173b56fa672e5bb8baa5456f2cf1eb47887 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 519de98ce..e6bef8b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,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 ## [13.0.5] - 2025-08-24 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 db556cfdd..3eb435c11 100644 --- a/test/io/postgrest.py +++ b/test/io/postgrest.py @@ -96,7 +96,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) @@ -104,7 +106,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() @@ -218,3 +221,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 886cf82ba..17ac68b90 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -1355,9 +1355,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( @@ -1365,7 +1365,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):