Provide for forward compatibility with future minor protocol versions.

Previously, any attempt to request a 3.x protocol version other than
3.0 would lead to a hard connection failure, which made the minor
protocol version really no different from the major protocol version
and precluded gentle protocol version breaks.  Instead, when the
client requests a 3.x protocol version where x is greater than 0, send
the new NegotiateProtocolVersion message to convey that we support
only 3.0.  This makes it possible to introduce new minor protocol
versions without requiring a connection retry when the server is
older.

In addition, if the startup packet includes name/value pairs where
the name starts with "_pq_.", assume that those are protocol options,
not GUCs.  Include those we don't support (i.e. all of them, at
present) in the NegotiateProtocolVersion message so that the client
knows they were not understood.  This makes it possible for the
client to request previously-unsupported features without bumping
the protocol version at all; the client can tell from the server's
response whether the option was understood.

It will take some time before servers that support these new
facilities become common in the wild; to speed things up and make
things easier for a future 3.1 protocol version, back-patch to all
supported releases.

Robert Haas and Badrul Chowdhury

Discussion: http://postgr.es/m/BN6PR21MB0772FFA0CBD298B76017744CD1730@BN6PR21MB0772.namprd21.prod.outlook.com
Discussion: http://postgr.es/m/30788.1498672033@sss.pgh.pa.us
This commit is contained in:
Robert Haas 2017-11-21 13:56:24 -05:00
parent f3b0897a12
commit ae65f6066d
2 changed files with 159 additions and 16 deletions

View File

@ -22,10 +22,18 @@
<productname>PostgreSQL</productname> 7.4 and later. For descriptions
of the earlier protocol versions, see previous releases of the
<productname>PostgreSQL</productname> documentation. A single server
can support multiple protocol versions. The initial
startup-request message tells the server which protocol version the
client is attempting to use, and then the server follows that protocol
if it is able.
can support multiple protocol versions. The initial startup-request
message tells the server which protocol version the client is attempting to
use. If the major version requested by the client is not supported by
the server, the connection will be rejected (for example, this would occur
if the client requested protocol version 4.0, which does not exist as of
this writing). If the minor version requested by the client is not
supported by the server (e.g. the client requests version 3.1, but the
server supports only 3.0), the server may either reject the connection or
may respond with a NegotiateProtocolVersion message containing the highest
minor protocol version which it supports. The client may then choose either
to continue with the connection using the specified protocol version or
to abort the connection.
</para>
<para>
@ -406,6 +414,21 @@
</listitem>
</varlistentry>
<varlistentry>
<term>NegotiateProtocolVersion</term>
<listitem>
<para>
The server does not support the minor protocol version requested
by the client, but does support an earlier version of the protocol;
this message indicates the highest supported minor version. This
message will also be sent if the client requested unsupported protocol
options (i.e. beginning with <literal>_pq_.</literal>) in the
startup packet. This message will be followed by an ErrorResponse or
a message indicating the success or failure of authentication.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
@ -420,8 +443,10 @@
for further messages from the server. In this phase a backend process
is being started, and the frontend is just an interested bystander.
It is still possible for the startup attempt
to fail (ErrorResponse), but in the normal case the backend will send
some ParameterStatus messages, BackendKeyData, and finally ReadyForQuery.
to fail (ErrorResponse) or the server to decline support for the requested
minor protocol version (NegotiateProtocolVersion), but in the normal case
the backend will send some ParameterStatus messages, BackendKeyData, and
finally ReadyForQuery.
</para>
<para>
@ -4715,6 +4740,74 @@ GSSResponse (F)
</listitem>
</varlistentry>
<varlistentry>
<term>
NegotiateProtocolVersion (B)
</term>
<listitem>
<para>
<variablelist>
<varlistentry>
<term>
Byte1('v')
</term>
<listitem>
<para>
Identifies the message as a protocol version negotiation
message.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
Int32
</term>
<listitem>
<para>
Length of message contents in bytes, including self.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
Int32
</term>
<listitem>
<para>
Newest minor protocol version supported by the server
for the major protocol version requested by the client.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
Int32
</term>
<listitem>
<para>
Number of protocol options not recognized by the server.
</para>
</listitem>
</varlistentry>
</variablelist>
Then, for protocol option not recognized by the server, there
is the following:
<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
The option name.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
@ -5670,11 +5763,13 @@ StartupMessage (F)
</varlistentry>
</variablelist>
In addition to the above, any run-time parameter that can be
set at backend start time might be listed. Such settings
will be applied during backend start (after parsing the
command-line arguments if any). The values will act as
session defaults.
In addition to the above, others parameter may be listed.
Parameter names beginning with <literal>_pq_.</literal> are
reserved for use as protocol extensions, while others are
treated as run-time parameters to be set at backend start
time. Such settings will be applied during backend start
(after parsing the command-line arguments if any) and will
act as session defaults.
</para>
</listitem>
</varlistentry>

View File

@ -101,6 +101,7 @@
#include "lib/ilist.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pg_getopt.h"
@ -412,6 +413,7 @@ static void ExitPostmaster(int status) pg_attribute_noreturn();
static int ServerLoop(void);
static int BackendStartup(Port *port);
static int ProcessStartupPacket(Port *port, bool SSLdone);
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
static void processCancelRequest(Port *port, void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
@ -2052,12 +2054,9 @@ retry1:
*/
FrontendProtocol = proto;
/* Check we can handle the protocol the frontend is using. */
/* Check that the major protocol version is in range. */
if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) ||
PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) ||
(PG_PROTOCOL_MAJOR(proto) == PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) &&
PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)))
PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST))
ereport(FATAL,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u",
@ -2079,6 +2078,7 @@ retry1:
if (PG_PROTOCOL_MAJOR(proto) >= 3)
{
int32 offset = sizeof(ProtocolVersion);
List *unrecognized_protocol_options = NIL;
/*
* Scan packet body for name/option pairs. We can assume any string
@ -2128,6 +2128,16 @@ retry1:
valptr),
errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\".")));
}
else if (strncmp(nameptr, "_pq_.", 5) == 0)
{
/*
* Any option beginning with _pq_. is reserved for use as a
* protocol-level option, but at present no such options are
* defined.
*/
unrecognized_protocol_options =
lappend(unrecognized_protocol_options, pstrdup(nameptr));
}
else
{
/* Assume it's a generic GUC option */
@ -2147,6 +2157,16 @@ retry1:
ereport(FATAL,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("invalid startup packet layout: expected terminator as last byte")));
/*
* If the client requested a newer protocol version or if the client
* requested any protocol options we didn't recognize, let them know
* the newest minor protocol version we do support and the names of any
* unrecognized options.
*/
if (PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) ||
unrecognized_protocol_options != NIL)
SendNegotiateProtocolVersion(unrecognized_protocol_options);
}
else
{
@ -2260,6 +2280,34 @@ retry1:
return STATUS_OK;
}
/*
* Send a NegotiateProtocolVersion to the client. This lets the client know
* that they have requested a newer minor protocol version than we are able
* to speak. We'll speak the highest version we know about; the client can,
* of course, abandon the connection if that's a problem.
*
* We also include in the response a list of protocol options we didn't
* understand. This allows clients to include optional parameters that might
* be present either in newer protocol versions or third-party protocol
* extensions without fear of having to reconnect if those options are not
* understood, while at the same time making certain that the client is aware
* of which options were actually accepted.
*/
static void
SendNegotiateProtocolVersion(List *unrecognized_protocol_options)
{
StringInfoData buf;
ListCell *lc;
pq_beginmessage(&buf, 'v'); /* NegotiateProtocolVersion */
pq_sendint32(&buf, PG_PROTOCOL_LATEST);
pq_sendint32(&buf, list_length(unrecognized_protocol_options));
foreach(lc, unrecognized_protocol_options)
pq_sendstring(&buf, lfirst(lc));
pq_endmessage(&buf);
/* no need to flush, some other message will follow */
}
/*
* The client has sent a cancel request packet, not a normal