From 3863ef3db73ccd08b3972058a8bfd90ec8265d76 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH v15 4/9] libpq: Add max_protocol_version connection option All officially supported version of the PostgreSQL server send the NegotiateProtocolVersion message when an unsupported minor protocol version is requested by a client. But many other applications that implement the PostgreSQL protocol (connection poolers, or other databases) do not, and the same is true for many unspported PostgreSQL server versions. Connecting to such other applications thus fails if a client requests a protocol version different than 3.0. This patch adds a max_protocol_version connection option to libpq that specifies the protocol version that libpq should request from the server. Currently all allowed values result in the use of 3.0, but that will be changed in a future commit that bumps the protocol version. Even after that version bump the default will likely stay 3.0 for the time being. Once more of the ecosystem supports the NegotiateProtocolVersion message we might want to change the default to the latest minor version. --- doc/src/sgml/libpq.sgml | 37 +++++++++++++++++++ src/interfaces/libpq/fe-connect.c | 59 ++++++++++++++++++++++++++++++- src/interfaces/libpq/libpq-int.h | 2 ++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index a609a8cff5..a782340e61 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2335,6 +2335,33 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname + + + max_protocol_version + + + Specifies the protocol version to request from the server. + The default is to use version 3.0 of the + PostgreSQL protocol, unless the connection + string specifies a feature that relies on a higher protocol version, in + that case the latest version supported by libpq is used. If the server + does not support the requested protocol version of the client the + connection will be automatically downgraded to a lower minor protocol + version, which the server does support. After the connection attempt has + completed you can use to find + out which exact protocol version was negotiated. + + + + The current supported values are + 3.0 + and latest. The latest value is + equivalent to the latest protocol version that is supported by the used + libpq version, which currently is 3.0, but this will + change in future libpq releases. + + + @@ -9136,6 +9163,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) linkend="libpq-connect-load-balance-hosts"/> connection parameter. + + + + + PGMAXPROTOCOLVERSION + + PGMAXPROTOCOLVERSION behaves the same as the connection parameter. + + diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5c8096697c..73891a125c 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -364,6 +364,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Load-Balance-Hosts", "", 8, /* sizeof("disable") = 8 */ offsetof(struct pg_conn, load_balance_hosts)}, + {"max_protocol_version", "PGMAXPROTOCOLVERSION", + NULL, NULL, + "Max-Protocol-Version", "", 6, /* sizeof("latest") = 6 */ + offsetof(struct pg_conn, max_protocol_version)}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} @@ -1834,6 +1839,58 @@ pqConnectOptions2(PGconn *conn) } } + if (conn->max_protocol_version) + { + if (strcmp(conn->max_protocol_version, "latest") == 0) + { + conn->max_pversion = PG_PROTOCOL_LATEST; + } + else + { + char *end; + int major, + minor; + + major = strtol(conn->max_protocol_version, &end, 10); + if (*end != '.') + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + "max_protocol_version", + conn->max_protocol_version); + return false; + } + minor = strtol(&end[1], &end, 10); + if (*end != '\0') + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + "max_protocol_version", + conn->max_protocol_version); + return false; + } + conn->max_pversion = PG_PROTOCOL(major, minor); + if (conn->max_pversion > PG_PROTOCOL_LATEST || + conn->max_pversion < PG_PROTOCOL_EARLIEST) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + "max_protocol_version", + conn->max_protocol_version); + return false; + } + } + } + else + { + /* + * To not break connecting to older servers/poolers that do not yet + * support NegotiateProtocolVersion, default to the 3.0 protocol at + * least for a while longer. + */ + conn->max_pversion = PG_PROTOCOL(3, 0); + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -2836,7 +2893,7 @@ keep_going: /* We will come back to here until there is * must persist across individual connection attempts, but we must * reset them when we start to consider a new server. */ - conn->pversion = PG_PROTOCOL(3, 0); + conn->pversion = conn->max_pversion; conn->send_appname = true; conn->failed_enc_methods = 0; conn->current_enc_method = 0; diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 03e4da40ba..65b8df2efc 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -427,6 +427,7 @@ struct pg_conn char *target_session_attrs; /* desired session properties */ char *require_auth; /* name of the expected auth method */ char *load_balance_hosts; /* load balance over hosts */ + char *max_protocol_version; /* maximum used protocol version */ bool cancelRequest; /* true if this connection is used to send a * cancel request, instead of being a normal @@ -517,6 +518,7 @@ struct pg_conn AddrInfo *addr; /* the array of addresses for the currently * tried host */ bool send_appname; /* okay to send application_name? */ + ProtocolVersion max_pversion; /* protocol version to request */ /* Miscellaneous stuff */ int be_pid; /* PID of backend --- needed for cancels */ -- 2.34.1