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

portaddr allows the port a connection is made to 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.

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.

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

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index c2b078bd6a9..3f94d39bd33 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2860,6 +2860,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 7f4090e0d8d..af428dbf2f0 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,10 @@ 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 */
+	print_portaddr = (!is_unixsock_path(host) &&
+					  portaddr && *portaddr && strcmp(port, portaddr) != 0);
+
 	/* Determine the exact number of rows to print */
 	rows = 12;
 	cols = 2;
@@ -834,6 +843,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 +887,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);
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 69c38c6a367..5546073b770 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2464,6 +2464,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
@@ -3323,6 +3347,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;
 
@@ -3378,8 +3403,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)
 					{
@@ -3390,6 +3415,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
@@ -5244,6 +5278,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);
@@ -7744,6 +7779,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..820fc9aa842 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -415,6 +415,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 3ccb063d453..d18c9ce18b8 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

