public inbox for [email protected]  
help / color / mirror / Atom feed
Feature: implement NegotiateProtocolVersion message
3+ messages / 1 participants
[nested] [flat]

* Feature: implement NegotiateProtocolVersion message
@ 2025-07-08 02:21  Tatsuo Ishii <[email protected]>
  0 siblings, 1 reply; 3+ messages in thread

From: Tatsuo Ishii @ 2025-07-08 02:21 UTC (permalink / raw)
  To: [email protected]

NegotiateProtocolVersion message is a message sent from backend to
frontend. After a startup message is sent from frontend to backend,
and the minor protocol version in the startup message is not supported
by the backend, the backend sends back a NegotiateProtocolVersion
message along with the latest minor protocol version it supports.

For example, PostgreSQL 18 client could request a protocol version 3.2
against PostgreSQL 17 server, then the PG17 server sends back the
NegotiateProtocolVersion message with protocol version 3.0 since it
does not support 3.2. If the PG18 client accepts it, then from now on
the protocol version 3.0 is used for the communication.

At this point, as PG18's psql (or libpq) uses protocol version 3.0 by
default, PG18 (or before) server will not send back the
NegotiateProtocolVersion message because the versions support 3.0.

The only case when 3.2 is used, is PG18's psql (or libpq) uses a
connection option max_protocol_version (or environment variable
PGMAXPROTOCOLVERSION) being set to "3.2".  I don't know if the case
happen in the wild after PG18 is released, but I would like to prepare
for the situation by implementing NegotiateProtocolVersion message in
Pgpool-II 4.7. Attached patch does followings for the purpose.

- Add ProcessNegotiateProtocol() to pool_do_auth() to process the
  message while handling a startup message. It reads the message from
  all backend, then forward it to frontend. The message is saved in
  ConnectionInfo for the case below. It also save the major and minor
  protocol version after negotiation to POOL_CONNECTION_POOL_SLOT for
  later use. The version numbers are also saved to ConnectionInfo so
  that pcp_proc_info shows them.

- When a client connects to pgpool, it looks for cached connections in
  the connection: pgpool tries to match the startup message with the
  one in the connection pool. Since the startup message in the
  connection pool is saved at the time when the client connects to
  pgpool, it is possible that two connection cache are created with
  same user and database. Suppose there's a connection cache with a
  startup message having protocol version 3.0, and a new client tries
  to connect to pgpool using protocol version 3.2. Pgpool looks for a
  connection cache with the startup message having protocol version
  3.2, not 3.0. As a result a new connection cache entry is created
  with protocol version 3.2.

- When a client uses the connection pool, the saved
  NegotiateProtocolVersion message is sent to frontend to emulate the
  protocol negotiation.

- The frontend/backend protocol 3.2 changes the BackendKeyData message
  format but it's not implemented in this patch yet. I will work on it
  later on.

Best regards,
--
Tatsuo Ishii
SRA OSS K.K.
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp


Attachments:

  [application/octet-stream] v1-0001-Feature-implement-NegotiateProtocolVersion-messag.patch (8.2K, 2-v1-0001-Feature-implement-NegotiateProtocolVersion-messag.patch)
  download | inline diff:
From 54097f43d61d34071748ec5cfc0ed49469fdf149 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <[email protected]>
Date: Tue, 8 Jul 2025 11:19:29 +0900
Subject: [PATCH v1] Feature: implement NegotiateProtocolVersion message.

Implementing the message is necessary when frontend requests the
protocol version 3.2 (i.e. PostgreSQL 18+ or compatible clients),
while backend still only supports 3.0 (i.e. backend is PostgreSQL 17
or before).

This commit handles the message so that the message is forwarded from
backend to frontend when there's no connection cache exists.

If connection cache exists, pgpool sends the message, which has been
saved at the time when the connection cache was created, to frontend.

Note that the frontend/backend protocol 3.2 changes the BackendKeyData
message format, but it's not implemented in this commit yet. This
means that still pgpool cannot handle 3.2 protocol.
---
 src/auth/pool_auth.c                | 116 +++++++++++++++++++++++++++-
 src/include/pool.h                  |   9 +++
 src/protocol/pool_connection_pool.c |   2 +
 3 files changed, 123 insertions(+), 4 deletions(-)

diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c
index be9f33434..f0a376c27 100644
--- a/src/auth/pool_auth.c
+++ b/src/auth/pool_auth.c
@@ -79,6 +79,7 @@ static void authenticate_frontend_SCRAM(POOL_CONNECTION * backend, POOL_CONNECTI
 static void authenticate_frontend_clear_text(POOL_CONNECTION * frontend);
 static bool get_auth_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth,
 				  char **password, PasswordType *passwordType);
+static void ProcessNegotiateProtocol(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp);
 
 /*
  * Do authentication. Assuming the only caller is
@@ -342,6 +343,7 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 
 	protoMajor = MAIN_CONNECTION(cp)->sp->major;
 
+read_kind:
 	kind = pool_read_kind(cp);
 	if (kind < 0)
 		ereport(ERROR,
@@ -365,6 +367,12 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 				 errdetail("backend response with kind \'E\' when expecting \'R\'"),
 				 errhint("This issue can be caused by version mismatch (current version %d)", protoMajor)));
 	}
+	else if (kind == 'v')
+	{
+		/* NegotiateProtocolVersion received */
+		ProcessNegotiateProtocol(frontend, cp);
+		goto read_kind;
+	}
 	else if (kind != 'R')
 		ereport(ERROR,
 				(errmsg("backend authentication failed"),
@@ -597,8 +605,11 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 
 		}
 
-		send_auth_ok(frontend, protoMajor);
-		authkind = 0;
+		if (kind == 'R')
+		{
+			send_auth_ok(frontend, protoMajor);
+			authkind = 0;
+		}
 	}
 
 	else
@@ -756,7 +767,16 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 			CONNECTION_SLOT(cp, i)->key = cp->info[i].key = key;
 
 			cp->info[i].major = sp->major;
-			cp->info[i].minor = sp->minor;
+
+			/*
+			 * If NegotiateProtocol message has been received, set the minor
+			 * version. Othewise use the version in the StartupMessage.
+			 */
+			if (CONNECTION_SLOT(cp, i)->negotiated_minor >= 0)
+				cp->info[i].minor = CONNECTION_SLOT(cp, i)->negotiated_minor;
+			else
+				cp->info[i].minor = sp->minor;
+
 			strlcpy(cp->info[i].database, sp->database, sizeof(cp->info[i].database));
 			strlcpy(cp->info[i].user, sp->user, sizeof(cp->info[i].user));
 			cp->info[i].counter = 1;
@@ -779,16 +799,31 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 }
 
 /*
-* do re-authentication for reused connection. if success return 0 otherwise throws ereport.
+* do re-authentication for reused connection. if success return 0 otherwise
+* throws ereport.
 */
 int
 pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 {
 	int			protoMajor;
 	int			msglen;
+	POOL_CONNECTION_POOL_SLOT	*sp;
 
 	protoMajor = MAJOR(cp);
 
+	/*
+	 * If NegotiateProtocolMsg has been received from backend, forward it to
+	 * frontend. If the frontend dislike it, it will disconnect the
+	 * connection. Otherwise it will silently continue.
+	 */
+	sp = CONNECTION_SLOT(cp, MAIN_NODE_ID);
+	if (protoMajor == PROTO_MAJOR_V3 && sp->negotiateProtocolMsg)
+	{
+		elog(DEBUG1, "negotiateProtocol message is forwarded to frontend at reauth");
+		pool_write_and_flush(frontend, sp->negotiateProtocolMsg,
+							 sp->nplen);
+	}
+
 	/*
 	 * if hba is enabled we would already have passed authentication
 	 */
@@ -822,6 +857,9 @@ pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 		}
 	}
 
+	/*
+	 * Send auth ok
+	 */
 	pool_write(frontend, "R", 1);
 
 	if (protoMajor == PROTO_MAJOR_V3)
@@ -832,7 +870,10 @@ pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 
 	msglen = htonl(0);
 	pool_write_and_flush(frontend, &msglen, sizeof(msglen));
+
+	/* send BackendKeyData */
 	pool_send_backend_key_data(frontend, MAIN_CONNECTION(cp)->pid, MAIN_CONNECTION(cp)->key, protoMajor);
+
 	return 0;
 }
 
@@ -2074,3 +2115,70 @@ pg_SASL_continue(POOL_CONNECTION * backend, char *payload, int payloadlen, void
 
 	return 0;
 }
+
+/*
+ * Forward NegotiateProtocol message to frontend.
+ *
+ * When this function is called, message kind has been already read.
+ */
+static void
+ProcessNegotiateProtocol(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp)
+{
+	int32	len;
+	int32	savelen;
+	int32	protoMajor;
+	int32	protoMinor;
+	int32	protov;
+	bool	forwardMsg = false;
+	int		i;
+
+	elog(DEBUG1, "Forwarding NegotiateProtocol message to frontend");
+	pool_write(frontend, "v", 1);	/* forward message kind */
+	savelen = len = pool_read_int(cp);		/* message length including self */
+	pool_write(frontend, &len, 4);	/* forward message length */
+	len = ntohl(len) - 4;			/* length of rest of the message */
+	protov = pool_read_int(cp);	/* read protocol version */
+	protoMajor = ntohl(protov) >> 16;		/* protocol major version */
+	protoMinor = ntohl(protov) & 0x0000ffff;	/* protocol minor version */
+	pool_write(frontend, &protov, 4);	/* forward protocol version */
+	elog(DEBUG1, "protocol verion offered: major: %d minor: %d", protoMajor, protoMinor);
+	len -= 4;
+	for (i = 0; i < NUM_BACKENDS; i++)
+	{
+		if (VALID_BACKEND(i))
+		{
+			POOL_CONNECTION_POOL_SLOT	*sp;
+			char	*p;
+			char	*np;
+			Size	nplen;
+
+			p = pool_read2(CONNECTION(cp, i), len);
+			if (!forwardMsg)
+			{
+				pool_write_and_flush(frontend, p, len);	/* forward rest of message */
+				forwardMsg = true;
+			}
+			/* save negatiate protocol version */
+			sp = CONNECTION_SLOT(cp, i);
+			sp->negotiated_major = protoMajor;
+			sp->negotiated_minor = protoMinor;
+
+			/* save negatiate protocol message */
+			nplen = 1 +	/* message kind */
+				sizeof(savelen) +	/* message length */
+				sizeof(protov) +	/* protocol version */
+				len;				/* rest of message */
+			/* allocate message area */
+			sp->negotiateProtocolMsg = MemoryContextAlloc(TopMemoryContext, nplen);
+			np = sp->negotiateProtocolMsg;
+			sp->nplen = nplen;	/* set message length */
+
+			*np++ = 'v';
+			memcpy(np, &savelen, sizeof(savelen));
+			np += sizeof(savelen);
+			memcpy(np, &protov, sizeof(protov));
+			np += sizeof(protov);
+			memcpy(np, p, len);
+		}
+	}
+}
diff --git a/src/include/pool.h b/src/include/pool.h
index c9b4dc27e..cf8aff073 100644
--- a/src/include/pool.h
+++ b/src/include/pool.h
@@ -262,6 +262,15 @@ typedef struct
 	time_t		closetime;		/* absolute time in second when the connection
 								 * closed if 0, that means the connection is
 								 * under use. */
+	/*
+	 * Protocol version after negotiation. Negative value means no negotiation
+	 * has been done.
+	 */
+	int			negotiated_major;
+	int			negotiated_minor;
+	char		*negotiateProtocolMsg;	/* Raw NegotiateProtocol messag */
+	int32		nplen;			/* message length of NegotiateProtocol messag */
+
 }			POOL_CONNECTION_POOL_SLOT;
 
 typedef struct
diff --git a/src/protocol/pool_connection_pool.c b/src/protocol/pool_connection_pool.c
index 225294a1b..00046e687 100644
--- a/src/protocol/pool_connection_pool.c
+++ b/src/protocol/pool_connection_pool.c
@@ -235,6 +235,8 @@ pool_discard_cp(char *user, char *database, int protoMajor)
 		}
 		CONNECTION_SLOT(p, i)->sp = NULL;
 		pool_close(CONNECTION(p, i));
+		if (CONNECTION_SLOT(p, i)->negotiateProtocolMsg)
+			pfree(CONNECTION_SLOT(p, i)->negotiateProtocolMsg);
 		pfree(CONNECTION_SLOT(p, i));
 	}
 
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: Feature: implement NegotiateProtocolVersion message
@ 2025-07-09 07:00  Tatsuo Ishii <[email protected]>
  parent: Tatsuo Ishii <[email protected]>
  0 siblings, 1 reply; 3+ messages in thread

From: Tatsuo Ishii @ 2025-07-09 07:00 UTC (permalink / raw)
  To: [email protected]

> NegotiateProtocolVersion message is a message sent from backend to
> frontend. After a startup message is sent from frontend to backend,
> and the minor protocol version in the startup message is not supported
> by the backend, the backend sends back a NegotiateProtocolVersion
> message along with the latest minor protocol version it supports.
> 
> For example, PostgreSQL 18 client could request a protocol version 3.2
> against PostgreSQL 17 server, then the PG17 server sends back the
> NegotiateProtocolVersion message with protocol version 3.0 since it
> does not support 3.2. If the PG18 client accepts it, then from now on
> the protocol version 3.0 is used for the communication.
> 
> At this point, as PG18's psql (or libpq) uses protocol version 3.0 by
> default, PG18 (or before) server will not send back the
> NegotiateProtocolVersion message because the versions support 3.0.
> 
> The only case when 3.2 is used, is PG18's psql (or libpq) uses a
> connection option max_protocol_version (or environment variable
> PGMAXPROTOCOLVERSION) being set to "3.2".  I don't know if the case
> happen in the wild after PG18 is released, but I would like to prepare
> for the situation by implementing NegotiateProtocolVersion message in
> Pgpool-II 4.7. Attached patch does followings for the purpose.
> 
> - Add ProcessNegotiateProtocol() to pool_do_auth() to process the
>   message while handling a startup message. It reads the message from
>   all backend, then forward it to frontend. The message is saved in
>   ConnectionInfo for the case below. It also save the major and minor
>   protocol version after negotiation to POOL_CONNECTION_POOL_SLOT for
>   later use. The version numbers are also saved to ConnectionInfo so
>   that pcp_proc_info shows them.
> 
> - When a client connects to pgpool, it looks for cached connections in
>   the connection: pgpool tries to match the startup message with the
>   one in the connection pool. Since the startup message in the
>   connection pool is saved at the time when the client connects to
>   pgpool, it is possible that two connection cache are created with
>   same user and database. Suppose there's a connection cache with a
>   startup message having protocol version 3.0, and a new client tries
>   to connect to pgpool using protocol version 3.2. Pgpool looks for a
>   connection cache with the startup message having protocol version
>   3.2, not 3.0. As a result a new connection cache entry is created
>   with protocol version 3.2.
> 
> - When a client uses the connection pool, the saved
>   NegotiateProtocolVersion message is sent to frontend to emulate the
>   protocol negotiation.
> 
> - The frontend/backend protocol 3.2 changes the BackendKeyData message
>   format but it's not implemented in this patch yet. I will work on it
>   later on.

Attached is the v2 patch fixing bug in v1. Also some protocol related
macros are imported from PostgreSQL.

Best regards,
--
Tatsuo Ishii
SRA OSS K.K.
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp


Attachments:

  [application/octet-stream] v2-0001-Feature-implement-NegotiateProtocolVersion-messag.patch (10.9K, 2-v2-0001-Feature-implement-NegotiateProtocolVersion-messag.patch)
  download | inline diff:
From 766e738118e15a564e205429564cbfe1915d684e Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <[email protected]>
Date: Wed, 9 Jul 2025 15:58:16 +0900
Subject: [PATCH v2] Feature: implement NegotiateProtocolVersion message.

Implementing the message is necessary when frontend requests the
protocol version 3.2 (i.e. PostgreSQL 18+ or compatible clients),
while backend still only supports 3.0 (i.e. backend is PostgreSQL 17
or before).

This commit handles the message so that the message is forwarded from
backend to frontend when there's no connection cache exists.

If connection cache exists, pgpool sends the message, which has been
saved at the time when the connection cache was created, to frontend.

Note that the frontend/backend protocol 3.2 changes the BackendKeyData
message format, but it's not implemented in this commit yet. This
means that still pgpool cannot handle 3.2 protocol.

Discussion: https://www.postgresql.org/message-id/20250708.112133.1324153277751075866.ishii%40postgresql.org
---
 src/auth/pool_auth.c                | 116 +++++++++++++++++++++++++++-
 src/include/pool.h                  |  48 +++++++++++-
 src/protocol/child.c                |   4 +-
 src/protocol/pool_connection_pool.c |   4 +-
 4 files changed, 164 insertions(+), 8 deletions(-)

diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c
index be9f33434..54d646bc3 100644
--- a/src/auth/pool_auth.c
+++ b/src/auth/pool_auth.c
@@ -79,6 +79,7 @@ static void authenticate_frontend_SCRAM(POOL_CONNECTION * backend, POOL_CONNECTI
 static void authenticate_frontend_clear_text(POOL_CONNECTION * frontend);
 static bool get_auth_password(POOL_CONNECTION * backend, POOL_CONNECTION * frontend, int reauth,
 				  char **password, PasswordType *passwordType);
+static void ProcessNegotiateProtocol(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp);
 
 /*
  * Do authentication. Assuming the only caller is
@@ -342,6 +343,7 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 
 	protoMajor = MAIN_CONNECTION(cp)->sp->major;
 
+read_kind:
 	kind = pool_read_kind(cp);
 	if (kind < 0)
 		ereport(ERROR,
@@ -365,6 +367,12 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 				 errdetail("backend response with kind \'E\' when expecting \'R\'"),
 				 errhint("This issue can be caused by version mismatch (current version %d)", protoMajor)));
 	}
+	else if (kind == 'v')
+	{
+		/* NegotiateProtocolVersion received */
+		ProcessNegotiateProtocol(frontend, cp);
+		goto read_kind;
+	}
 	else if (kind != 'R')
 		ereport(ERROR,
 				(errmsg("backend authentication failed"),
@@ -597,8 +605,11 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 
 		}
 
-		send_auth_ok(frontend, protoMajor);
-		authkind = 0;
+		if (kind == 'R')
+		{
+			send_auth_ok(frontend, protoMajor);
+			authkind = 0;
+		}
 	}
 
 	else
@@ -756,7 +767,16 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 			CONNECTION_SLOT(cp, i)->key = cp->info[i].key = key;
 
 			cp->info[i].major = sp->major;
-			cp->info[i].minor = sp->minor;
+
+			/*
+			 * If NegotiateProtocol message has been received, set the minor
+			 * version. Othewise use the version in the StartupMessage.
+			 */
+			if (CONNECTION_SLOT(cp, i)->nplen > 0)
+				cp->info[i].minor = CONNECTION_SLOT(cp, i)->negotiated_minor;
+			else
+				cp->info[i].minor = sp->minor;
+
 			strlcpy(cp->info[i].database, sp->database, sizeof(cp->info[i].database));
 			strlcpy(cp->info[i].user, sp->user, sizeof(cp->info[i].user));
 			cp->info[i].counter = 1;
@@ -779,16 +799,31 @@ pool_do_auth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 }
 
 /*
-* do re-authentication for reused connection. if success return 0 otherwise throws ereport.
+* do re-authentication for reused connection. if success return 0 otherwise
+* throws ereport.
 */
 int
 pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 {
 	int			protoMajor;
 	int			msglen;
+	POOL_CONNECTION_POOL_SLOT	*sp;
 
 	protoMajor = MAJOR(cp);
 
+	/*
+	 * If NegotiateProtocolMsg has been received from backend, forward it to
+	 * frontend. If the frontend dislike it, it will disconnect the
+	 * connection. Otherwise it will silently continue.
+	 */
+	sp = CONNECTION_SLOT(cp, MAIN_NODE_ID);
+	if (protoMajor == PROTO_MAJOR_V3 && sp->nplen > 0)
+	{
+		elog(DEBUG1, "negotiateProtocol message is forwarded to frontend at reauth");
+		pool_write_and_flush(frontend, sp->negotiateProtocolMsg,
+							 sp->nplen);
+	}
+
 	/*
 	 * if hba is enabled we would already have passed authentication
 	 */
@@ -822,6 +857,9 @@ pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 		}
 	}
 
+	/*
+	 * Send auth ok
+	 */
 	pool_write(frontend, "R", 1);
 
 	if (protoMajor == PROTO_MAJOR_V3)
@@ -832,7 +870,10 @@ pool_do_reauth(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * cp)
 
 	msglen = htonl(0);
 	pool_write_and_flush(frontend, &msglen, sizeof(msglen));
+
+	/* send BackendKeyData */
 	pool_send_backend_key_data(frontend, MAIN_CONNECTION(cp)->pid, MAIN_CONNECTION(cp)->key, protoMajor);
+
 	return 0;
 }
 
@@ -2074,3 +2115,70 @@ pg_SASL_continue(POOL_CONNECTION * backend, char *payload, int payloadlen, void
 
 	return 0;
 }
+
+/*
+ * Forward NegotiateProtocol message to frontend.
+ *
+ * When this function is called, message kind has been already read.
+ */
+static void
+ProcessNegotiateProtocol(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp)
+{
+	int32	len;
+	int32	savelen;
+	int32	protoMajor;
+	int32	protoMinor;
+	int32	protov;
+	bool	forwardMsg = false;
+	int		i;
+
+	elog(DEBUG1, "Forwarding NegotiateProtocol message to frontend");
+	pool_write(frontend, "v", 1);	/* forward message kind */
+	savelen = len = pool_read_int(cp);		/* message length including self */
+	pool_write(frontend, &len, 4);	/* forward message length */
+	len = ntohl(len) - 4;			/* length of rest of the message */
+	protov = pool_read_int(cp);	/* read protocol version */
+	protoMajor = PG_PROTOCOL_MAJOR(ntohl(protov));		/* protocol major version */
+	protoMinor = PG_PROTOCOL_MINOR(ntohl(protov));	/* protocol minor version */
+	pool_write(frontend, &protov, 4);	/* forward protocol version */
+	elog(DEBUG1, "protocol verion offered: major: %d minor: %d", protoMajor, protoMinor);
+	len -= 4;
+	for (i = 0; i < NUM_BACKENDS; i++)
+	{
+		if (VALID_BACKEND(i))
+		{
+			POOL_CONNECTION_POOL_SLOT	*sp;
+			char	*p;
+			char	*np;
+			Size	nplen;
+
+			p = pool_read2(CONNECTION(cp, i), len);
+			if (!forwardMsg)
+			{
+				pool_write_and_flush(frontend, p, len);	/* forward rest of message */
+				forwardMsg = true;
+			}
+			/* save negatiate protocol version */
+			sp = CONNECTION_SLOT(cp, i);
+			sp->negotiated_major = protoMajor;
+			sp->negotiated_minor = protoMinor;
+
+			/* save negatiate protocol message */
+			nplen = 1 +	/* message kind */
+				sizeof(savelen) +	/* message length */
+				sizeof(protov) +	/* protocol version */
+				len;				/* rest of message */
+			/* allocate message area */
+			sp->negotiateProtocolMsg = MemoryContextAlloc(TopMemoryContext, nplen);
+			np = sp->negotiateProtocolMsg;
+			sp->nplen = nplen;	/* set message length */
+
+			*np++ = 'v';
+			memcpy(np, &savelen, sizeof(savelen));
+			np += sizeof(savelen);
+			memcpy(np, &protov, sizeof(protov));
+			np += sizeof(protov);
+			memcpy(np, p, len);
+		}
+	}
+}
diff --git a/src/include/pool.h b/src/include/pool.h
index c9b4dc27e..28cf1757c 100644
--- a/src/include/pool.h
+++ b/src/include/pool.h
@@ -4,7 +4,9 @@
  * pgpool: a language independent connection pool server for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2003-2024	PgPool Global Development Group
+ * Portions Copyright (c) 2003-2025	PgPool Global Development Group
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -103,6 +105,41 @@ typedef enum
 	POOL_SOCKET_EOF
 }			POOL_SOCKET_STATE;
 
+/*
+ * Imported from src/include/libpq/pqcomm.h as of PostgreSQL 18.
+ *
+ * These manipulate the frontend/backend protocol version number.
+ *
+ * The major number should be incremented for incompatible changes.  The minor
+ * number should be incremented for compatible changes (eg. additional
+ * functionality).
+ *
+ * If a backend supports version m.n of the protocol it must actually support
+ * versions m.[0..n].  Backend support for version m-1 can be dropped after a
+ * `reasonable' length of time.
+ *
+ * A frontend isn't required to support anything other than the current
+ * version.
+ */
+
+#define PG_PROTOCOL_MAJOR(v)	((v) >> 16)
+#define PG_PROTOCOL_MINOR(v)	((v) & 0x0000ffff)
+#define PG_PROTOCOL_FULL(v)	(PG_PROTOCOL_MAJOR(v) * 10000 + PG_PROTOCOL_MINOR(v))
+#define PG_PROTOCOL(m,n)	(((m) << 16) | (n))
+
+/*
+ * The earliest and latest frontend/backend protocol version supported.
+ */
+
+#define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(3,0)
+#define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,2)
+
+typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
+
+typedef ProtocolVersion MsgType;
+
+/* end of importing */
+
 /* protocol major version numbers */
 #define PROTO_MAJOR_V2	2
 #define PROTO_MAJOR_V3	3
@@ -262,6 +299,15 @@ typedef struct
 	time_t		closetime;		/* absolute time in second when the connection
 								 * closed if 0, that means the connection is
 								 * under use. */
+	/*
+	 * Protocol version after negotiation. If nplen == 0, no negotiation has
+	 * been done.
+	 */
+	int			negotiated_major;
+	int			negotiated_minor;
+	char		*negotiateProtocolMsg;	/* Raw NegotiateProtocol messag */
+	int32		nplen;			/* message length of NegotiateProtocol messag */
+
 }			POOL_CONNECTION_POOL_SLOT;
 
 typedef struct
diff --git a/src/protocol/child.c b/src/protocol/child.c
index f4142f90d..7aea33540 100644
--- a/src/protocol/child.c
+++ b/src/protocol/child.c
@@ -637,8 +637,8 @@ read_startup_packet(POOL_CONNECTION * cp)
 
 	sp->len = len;
 	memcpy(&protov, sp->startup_packet, sizeof(protov));
-	sp->major = ntohl(protov) >> 16;
-	sp->minor = ntohl(protov) & 0x0000ffff;
+	sp->major = PG_PROTOCOL_MAJOR(ntohl(protov));
+	sp->minor = PG_PROTOCOL_MINOR(ntohl(protov));
 	cp->protoVersion = sp->major;
 
 	switch (sp->major)
diff --git a/src/protocol/pool_connection_pool.c b/src/protocol/pool_connection_pool.c
index 225294a1b..666187216 100644
--- a/src/protocol/pool_connection_pool.c
+++ b/src/protocol/pool_connection_pool.c
@@ -235,6 +235,8 @@ pool_discard_cp(char *user, char *database, int protoMajor)
 		}
 		CONNECTION_SLOT(p, i)->sp = NULL;
 		pool_close(CONNECTION(p, i));
+		if (CONNECTION_SLOT(p, i)->negotiateProtocolMsg)
+			pfree(CONNECTION_SLOT(p, i)->negotiateProtocolMsg);
 		pfree(CONNECTION_SLOT(p, i));
 	}
 
@@ -945,7 +947,7 @@ static POOL_CONNECTION_POOL * new_connection(POOL_CONNECTION_POOL * p)
 			continue;
 		}
 
-		s = palloc(sizeof(POOL_CONNECTION_POOL_SLOT));
+		s = palloc0(sizeof(POOL_CONNECTION_POOL_SLOT));
 
 		if (create_cp(s, i) == NULL)
 		{
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: Feature: implement NegotiateProtocolVersion message
@ 2025-07-10 10:38  Tatsuo Ishii <[email protected]>
  parent: Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 3+ messages in thread

From: Tatsuo Ishii @ 2025-07-10 10:38 UTC (permalink / raw)
  To: [email protected]

>> NegotiateProtocolVersion message is a message sent from backend to
>> frontend. After a startup message is sent from frontend to backend,
>> and the minor protocol version in the startup message is not supported
>> by the backend, the backend sends back a NegotiateProtocolVersion
>> message along with the latest minor protocol version it supports.
>> 
>> For example, PostgreSQL 18 client could request a protocol version 3.2
>> against PostgreSQL 17 server, then the PG17 server sends back the
>> NegotiateProtocolVersion message with protocol version 3.0 since it
>> does not support 3.2. If the PG18 client accepts it, then from now on
>> the protocol version 3.0 is used for the communication.
>> 
>> At this point, as PG18's psql (or libpq) uses protocol version 3.0 by
>> default, PG18 (or before) server will not send back the
>> NegotiateProtocolVersion message because the versions support 3.0.
>> 
>> The only case when 3.2 is used, is PG18's psql (or libpq) uses a
>> connection option max_protocol_version (or environment variable
>> PGMAXPROTOCOLVERSION) being set to "3.2".  I don't know if the case
>> happen in the wild after PG18 is released, but I would like to prepare
>> for the situation by implementing NegotiateProtocolVersion message in
>> Pgpool-II 4.7. Attached patch does followings for the purpose.
>> 
>> - Add ProcessNegotiateProtocol() to pool_do_auth() to process the
>>   message while handling a startup message. It reads the message from
>>   all backend, then forward it to frontend. The message is saved in
>>   ConnectionInfo for the case below. It also save the major and minor
>>   protocol version after negotiation to POOL_CONNECTION_POOL_SLOT for
>>   later use. The version numbers are also saved to ConnectionInfo so
>>   that pcp_proc_info shows them.
>> 
>> - When a client connects to pgpool, it looks for cached connections in
>>   the connection: pgpool tries to match the startup message with the
>>   one in the connection pool. Since the startup message in the
>>   connection pool is saved at the time when the client connects to
>>   pgpool, it is possible that two connection cache are created with
>>   same user and database. Suppose there's a connection cache with a
>>   startup message having protocol version 3.0, and a new client tries
>>   to connect to pgpool using protocol version 3.2. Pgpool looks for a
>>   connection cache with the startup message having protocol version
>>   3.2, not 3.0. As a result a new connection cache entry is created
>>   with protocol version 3.2.
>> 
>> - When a client uses the connection pool, the saved
>>   NegotiateProtocolVersion message is sent to frontend to emulate the
>>   protocol negotiation.
>> 
>> - The frontend/backend protocol 3.2 changes the BackendKeyData message
>>   format but it's not implemented in this patch yet. I will work on it
>>   later on.
> 
> Attached is the v2 patch fixing bug in v1. Also some protocol related
> macros are imported from PostgreSQL.

v2 patch pushed.

Best regards,
--
Tatsuo Ishii
SRA OSS K.K.
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp






^ permalink  raw  reply  [nested|flat] 3+ messages in thread


end of thread, other threads:[~2025-07-10 10:38 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-07-08 02:21 Feature: implement NegotiateProtocolVersion message Tatsuo Ishii <[email protected]>
2025-07-09 07:00 ` Tatsuo Ishii <[email protected]>
2025-07-10 10:38   ` Tatsuo Ishii <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox