From 370d2c794694c341049a87c900eb675740fdda24 Mon Sep 17 00:00:00 2001
From: Diego <mrstephenamell@gmail.com>
Date: Mon, 27 Jul 2026 14:06:48 -0300
Subject: [PATCH v3 2/2] libpq: Add PQportaddr(), and show the port address in
 psql \conninfo

portaddr lets the port a connection is made to differ from the port that
identifies the server, but there was no way to ask libpq which port it
actually used.  Add PQportaddr(), which reports the port of the current
connection just as PQhostaddr() reports its IP address; like that
function, it reports nothing for Unix-domain socket connections, which
have no port.  A LIBPQ_HAS_PORTADDR feature macro is provided, per the
convention for new libpq API.

Use it in psql's \conninfo, which grows a "Port Address" row displayed
only when the port connected to differs from the port identifying the
server -- the same rule the existing "Host Address" row follows.  No
Unix-socket test is needed, or wanted: PQportaddr() is already empty for
socket connections, and a TCP connection can exist even with a
socket-path host when hostaddr forces it.  The \connect success banner
likewise reports the port address when it differs, mirroring what
commit 6e5f8d489a did for hostaddr.

Discussion: https://postgr.es/m/001a6f1d-4adb-42b2-8bf6-44154ed0ab97@gmail.com
---
 doc/src/sgml/libpq.sgml                | 24 ++++++++++
 src/bin/psql/command.c                 | 63 +++++++++++++++++++++++---
 src/interfaces/libpq/exports.txt       |  1 +
 src/interfaces/libpq/fe-connect.c      | 52 ++++++++++++++++++++-
 src/interfaces/libpq/libpq-fe.h        |  5 ++
 src/interfaces/libpq/libpq-int.h       |  1 +
 src/interfaces/libpq/t/007_portaddr.pl | 26 +++++++++++
 7 files changed, 163 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6b3adb2e449..bf0758372eb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2863,6 +2863,30 @@ char *PQport(const PGconn *conn);
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQportaddr">
+     <term><function>PQportaddr</function><indexterm><primary>PQportaddr</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Returns the port that the active connection was actually made to.
+       This can be the port given by the <literal>port</literal> parameter,
+       or a port provided through the <literal>portaddr</literal> parameter.
+<synopsis>
+char *PQportaddr(const PGconn *conn);
+</synopsis>
+      </para>
+
+      <para>
+       <xref linkend="libpq-PQportaddr"/> returns <symbol>NULL</symbol> if the
+       <parameter>conn</parameter> argument is <symbol>NULL</symbol>.
+       Otherwise, if there is an error producing the port information (perhaps
+       if the connection has not been fully established or there was an
+       error), or if the connection is made over a Unix-domain socket, it
+       returns an empty string.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQtty">
      <term><function>PQtty</function><indexterm><primary>PQtty</primary></indexterm></term>
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 267c8c7d8b8..66851ed6119 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -794,6 +794,9 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
 	char	   *host;
 	bool		print_hostaddr;
 	char	   *hostaddr;
+	bool		print_portaddr;
+	char	   *port,
+			   *portaddr;
 	char	   *protocol_version,
 			   *backend_pid;
 	int			ssl_in_use,
@@ -815,6 +818,8 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
 	/* Get values for the parameters */
 	host = PQhost(pset.db);
 	hostaddr = PQhostaddr(pset.db);
+	port = PQport(pset.db);
+	portaddr = PQportaddr(pset.db);
 	version_num = PQfullProtocolVersion(pset.db);
 	protocol_version = psprintf("%d.%d", version_num / 10000,
 								version_num % 10000);
@@ -827,6 +832,14 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
 	print_hostaddr = (!is_unixsock_path(host) &&
 					  hostaddr && *hostaddr && strcmp(host, hostaddr) != 0);
 
+	/*
+	 * Likewise for the port actually connected to.  No is_unixsock_path()
+	 * test is needed here: PQportaddr() returns an empty string for
+	 * Unix-domain socket connections, and a TCP connection can be made even
+	 * with a socket-path host when hostaddr forces it.
+	 */
+	print_portaddr = (portaddr && *portaddr && strcmp(port, portaddr) != 0);
+
 	/* Determine the exact number of rows to print */
 	rows = 12;
 	cols = 2;
@@ -834,6 +847,8 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
 		rows += 6;
 	if (print_hostaddr)
 		rows++;
+	if (print_portaddr)
+		rows++;
 
 	/* Set it all up */
 	printTableInit(&cont, &pset.popt.topt, _("Connection Information"), cols, rows);
@@ -876,7 +891,12 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
 
 	/* Server Port */
 	printTableAddCell(&cont, _("Server Port"), false, false);
-	printTableAddCell(&cont, PQport(pset.db), false, false);
+	printTableAddCell(&cont, port, false, false);
+	if (print_portaddr)
+	{
+		printTableAddCell(&cont, _("Port Address"), false, false);
+		printTableAddCell(&cont, portaddr, false, false);
+	}
 
 	/* Options */
 	printTableAddCell(&cont, _("Options"), false, false);
@@ -4350,13 +4370,30 @@ do_connect(enum trivalue reuse_previous_specification,
 		{
 			char	   *connhost = PQhost(pset.db);
 			char	   *hostaddr = PQhostaddr(pset.db);
+			char	   *portaddr = PQportaddr(pset.db);
+			bool		show_portaddr;
+
+			/*
+			 * Show the port actually connected to when it differs from the
+			 * port that identifies the server.  PQportaddr() returns an
+			 * empty string for Unix-domain socket connections, so this
+			 * cannot fire for them.
+			 */
+			show_portaddr = (portaddr && *portaddr &&
+							 strcmp(PQport(pset.db), portaddr) != 0);
 
 			if (is_unixsock_path(connhost))
 			{
 				/* hostaddr overrides connhost */
 				if (hostaddr && *hostaddr)
-					printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
-						   PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db));
+				{
+					if (show_portaddr)
+						printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\" (port address \"%s\").\n"),
+							   PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db), portaddr);
+					else
+						printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
+							   PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db));
+				}
 				else
 					printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
 						   PQdb(pset.db), PQuser(pset.db), connhost, PQport(pset.db));
@@ -4364,11 +4401,23 @@ do_connect(enum trivalue reuse_previous_specification,
 			else
 			{
 				if (hostaddr && *hostaddr && strcmp(connhost, hostaddr) != 0)
-					printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"),
-						   PQdb(pset.db), PQuser(pset.db), connhost, hostaddr, PQport(pset.db));
+				{
+					if (show_portaddr)
+						printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\" (port address \"%s\").\n"),
+							   PQdb(pset.db), PQuser(pset.db), connhost, hostaddr, PQport(pset.db), portaddr);
+					else
+						printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"),
+							   PQdb(pset.db), PQuser(pset.db), connhost, hostaddr, PQport(pset.db));
+				}
 				else
-					printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
-						   PQdb(pset.db), PQuser(pset.db), connhost, PQport(pset.db));
+				{
+					if (show_portaddr)
+						printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\" (port address \"%s\").\n"),
+							   PQdb(pset.db), PQuser(pset.db), connhost, PQport(pset.db), portaddr);
+					else
+						printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
+							   PQdb(pset.db), PQuser(pset.db), connhost, PQport(pset.db));
+				}
 			}
 		}
 		else
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 1e3d5bd5867..2ac1198bc00 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -211,3 +211,4 @@ PQdefaultAuthDataHook     208
 PQfullProtocolVersion     209
 appendPQExpBufferVA       210
 PQgetThreadLock           211
+PQportaddr                212
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 18acbeb0795..b993dc65414 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2462,6 +2462,30 @@ getHostaddr(PGconn *conn, char *host_addr, int host_addr_len)
 		host_addr[0] = '\0';
 }
 
+/* ----------
+ * getPortaddr -
+ * Form the port number of the current connection, in the same way that
+ * getHostaddr() forms its IP address.  conn->raddr must be valid.  Nothing
+ * is reported for Unix-domain sockets, which have no port number.
+ * ----------
+ */
+static void
+getPortaddr(PGconn *conn, char *port_str, int port_str_len)
+{
+	struct sockaddr_storage *addr = &conn->raddr.addr;
+
+	if (addr->ss_family == AF_INET || addr->ss_family == AF_INET6)
+	{
+		if (pg_getnameinfo_all(addr, conn->raddr.salen,
+							   NULL, 0,
+							   port_str, port_str_len,
+							   NI_NUMERICSERV) != 0)
+			port_str[0] = '\0';
+	}
+	else
+		port_str[0] = '\0';
+}
+
 /*
  * emitHostIdentityInfo -
  * Speculatively append "connection to server so-and-so failed: " to
@@ -3321,6 +3345,7 @@ keep_going:						/* We will come back to here until there is
 				 */
 				{
 					char		host_addr[NI_MAXHOST];
+					char		port_str[NI_MAXSERV];
 					int			sock_type;
 					AddrInfo   *addr_cur;
 
@@ -3376,8 +3401,8 @@ keep_going:						/* We will come back to here until there is
 						goto error_return;
 
 					/*
-					 * Set connip, too.  Note we purposely ignore strdup
-					 * failure; not a big problem if it fails.
+					 * Set connip and connport, too.  Note we purposely ignore
+					 * strdup failure; not a big problem if it fails.
 					 */
 					if (conn->connip != NULL)
 					{
@@ -3388,6 +3413,15 @@ keep_going:						/* We will come back to here until there is
 					if (host_addr[0])
 						conn->connip = strdup(host_addr);
 
+					if (conn->connport != NULL)
+					{
+						free(conn->connport);
+						conn->connport = NULL;
+					}
+					getPortaddr(conn, port_str, NI_MAXSERV);
+					if (port_str[0])
+						conn->connport = strdup(port_str);
+
 					/* Try to create the socket */
 					sock_type = SOCK_STREAM;
 #ifdef SOCK_CLOEXEC
@@ -5242,6 +5276,7 @@ freePGconn(PGconn *conn)
 	free(conn->events);
 	pqReleaseConnHosts(conn);
 	free(conn->connip);
+	free(conn->connport);
 	release_conn_addrinfo(conn);
 	free(conn->scram_client_key_binary);
 	free(conn->scram_server_key_binary);
@@ -7742,6 +7777,19 @@ PQport(const PGconn *conn)
 	return DEF_PGPORT_STR;
 }
 
+char *
+PQportaddr(const PGconn *conn)
+{
+	if (!conn)
+		return NULL;
+
+	/* Return the port actually connected to */
+	if (conn->connhost != NULL && conn->connport != NULL)
+		return conn->connport;
+
+	return "";
+}
+
 /*
  * No longer does anything, but the function remains for API backwards
  * compatibility.
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 8ecb9b4a4c7..0389f30a101 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -69,6 +69,10 @@ extern "C"
 /* Indicates presence of the PQAUTHDATA_OAUTH_BEARER_TOKEN_V2 authdata hook */
 #define LIBPQ_HAS_OAUTH_BEARER_TOKEN_V2 1
 
+/* Features added in PostgreSQL v20: */
+/* Indicates presence of PQportaddr and the portaddr connection parameter */
+#define LIBPQ_HAS_PORTADDR 1
+
 /*
  * Option flags for PQcopyResult
  */
@@ -415,6 +419,7 @@ extern char *PQpass(const PGconn *conn);
 extern char *PQhost(const PGconn *conn);
 extern char *PQhostaddr(const PGconn *conn);
 extern char *PQport(const PGconn *conn);
+extern char *PQportaddr(const PGconn *conn);
 extern char *PQtty(const PGconn *conn);
 extern char *PQoptions(const PGconn *conn);
 extern ConnStatusType PQstatus(const PGconn *conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index f06f418c017..53c8950c221 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -490,6 +490,7 @@ struct pg_conn
 	int			whichhost;		/* host we're currently trying/connected to */
 	pg_conn_host *connhost;		/* details about each named host */
 	char	   *connip;			/* IP address for current network connection */
+	char	   *connport;		/* port number for current network connection */
 
 	/*
 	 * The pending command queue as a singly-linked list.  Head is the command
diff --git a/src/interfaces/libpq/t/007_portaddr.pl b/src/interfaces/libpq/t/007_portaddr.pl
index 5ecb573d8a7..856101c9541 100644
--- a/src/interfaces/libpq/t/007_portaddr.pl
+++ b/src/interfaces/libpq/t/007_portaddr.pl
@@ -48,6 +48,32 @@ $node->connect_ok(
 	sql => "\\echo :PORT",
 	expected_stdout => qr/^$unusedport$/);
 
+# PQportaddr() reports the port actually connected to, and psql shows it in
+# \conninfo when it differs from the port identifying the server.
+my ($ret, $stdout, $stderr) = $node->psql(
+	'postgres',
+	"\\conninfo",
+	extra_params => ['-w'],
+	connstr => "host=127.0.0.1 port=$unusedport portaddr=$realport",
+	on_error_stop => 0);
+is($ret, 0, "\\conninfo with portaddr succeeds");
+like($stdout, qr/^Server Port\|$unusedport$/m,
+	"\\conninfo reports port as the server port");
+like($stdout, qr/^Port Address\|$realport$/m,
+	"\\conninfo reports portaddr as the port address");
+
+($ret, $stdout, $stderr) = $node->psql(
+	'postgres',
+	"\\conninfo",
+	extra_params => ['-w'],
+	connstr => "host=127.0.0.1 port=$realport",
+	on_error_stop => 0);
+is($ret, 0, "\\conninfo without portaddr succeeds");
+like($stdout, qr/^Server Port\|$realport$/m,
+	"\\conninfo without portaddr reports the port");
+unlike($stdout, qr/Port Address/,
+	"\\conninfo omits the port address when it matches port");
+
 # An empty portaddr means "connect to port", the historical behavior.
 $node->connect_ok(
 	"host=127.0.0.1 port=$realport portaddr=",
-- 
2.43.0

