From 735ff6010f0d67a5323bfa2037153606b0a1b9c8 Mon Sep 17 00:00:00 2001 From: Tatsuo Ishii Date: Mon, 5 Oct 2026 13:39:02 +0900 Subject: [PATCH v2 3/3] Modify pgproto as a test driver. Also add 045.remove_proto_v2 regression test. Author: Tatsuo Ishii Backpatch-through: master only --- src/include/pgproto/pgproto.h | 3 +- src/include/pgproto/read.h | 7 +- src/include/pgproto/send.h | 4 +- .../tests/045.remove_proto_v2/expected.txt | 2 + .../tests/045.remove_proto_v2/test.sh | 52 ++++ src/tools/pgproto/main.c | 259 +++++++++++++++++- src/tools/pgproto/read.c | 26 +- src/tools/pgproto/send.c | 18 +- 8 files changed, 347 insertions(+), 24 deletions(-) create mode 100644 src/test/regression/tests/045.remove_proto_v2/expected.txt create mode 100755 src/test/regression/tests/045.remove_proto_v2/test.sh diff --git a/src/include/pgproto/pgproto.h b/src/include/pgproto/pgproto.h index 9be981572..314506197 100644 --- a/src/include/pgproto/pgproto.h +++ b/src/include/pgproto/pgproto.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2017-2018 Tatsuo Ishii + * Portions Copyright (c) 2018-2026 PgPool Global Development Group + * Portions Copyright (c) 2017-2018 Tatsuo Ishii * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby diff --git a/src/include/pgproto/read.h b/src/include/pgproto/read.h index c7f2dd0ce..a74d5b208 100644 --- a/src/include/pgproto/read.h +++ b/src/include/pgproto/read.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2017 Tatsuo Ishii + * Portions Copyright (c) 2018-2026 PgPool Global Development Group + * Portions Copyright (c) 2017-2018 Tatsuo Ishii * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -16,5 +17,9 @@ #ifndef READ_H #define READ_H +#include + extern void read_until_ready_for_query(PGconn *conn, int check_input, int wait_for_ready_for_query); +extern void read_internal(int fd, char *buf, int len, bool noerr); + #endif diff --git a/src/include/pgproto/send.h b/src/include/pgproto/send.h index 9ce8d9dcc..0c41f2435 100644 --- a/src/include/pgproto/send.h +++ b/src/include/pgproto/send.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2017 Tatsuo Ishii + * Portions Copyright (c) 2018-2026 PgPool Global Development Group + * Portions Copyright (c) 2017-2018 Tatsuo Ishii * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -21,5 +22,6 @@ extern void send_int(int intval, PGconn *conn); extern void send_int16(short shortval, PGconn *conn); extern void send_string(char *buf, PGconn *conn); extern void send_byte(char *buf, int len, PGconn *conn); +extern void write_it(int fd, void *buf, int len); #endif diff --git a/src/test/regression/tests/045.remove_proto_v2/expected.txt b/src/test/regression/tests/045.remove_proto_v2/expected.txt new file mode 100644 index 000000000..009b8d478 --- /dev/null +++ b/src/test/regression/tests/045.remove_proto_v2/expected.txt @@ -0,0 +1,2 @@ +<= BE ErrorResponse(FATAL: unsupported frontend protocol 2.0 +) diff --git a/src/test/regression/tests/045.remove_proto_v2/test.sh b/src/test/regression/tests/045.remove_proto_v2/test.sh new file mode 100755 index 000000000..01942ac6d --- /dev/null +++ b/src/test/regression/tests/045.remove_proto_v2/test.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------- +# Test script for v2 protocol acceptance. +# From Pgpool-II 4.8, it does not accept v2 frontend/backend protocol anymore. +# This test simply send a v2 startup packet to pgpool using pgproto +# and see if pgpool rejects it. +# Note that this requires version 4.8 or later Pgpool-II (including +# pgproto) and PostgreSQL 14 or later. +# +# +source $TESTLIBS +TESTDIR=testdir +PSQL=$PGBIN/psql +PG_CTL=$PGBIN/pg_ctl +PGPROTO=$PGPOOL_INSTALL_DIR/bin/pgproto +export PGDATABASE=test +export PGUSER=$USER + +# pgpool version number (e.g. "4.8") +vnum=`echo $PGPOOL_VERSION|awk '{print $3}'|tr -d 'a-zA-Z'` +if [ $(echo "$vnum < 4.8" | bc) -eq 1 ]; then + echo "test skipped. Pgpool-II version must be greater than or equal to 4.8" + exit 0 +fi + +# This test is only valid with PostgreSQL 14 or later. +if [ $PGVERSION -le 13 ];then + echo "test skipped. PostgreSQL version must be greater than or equal to 14" + exit 0 +fi + +rm -fr $TESTDIR +mkdir $TESTDIR +cd $TESTDIR + +echo -n "creating test environment..." +$PGPOOL_SETUP || exit 1 +echo "done." +source ./bashrc.ports +./startall +wait_for_pgpool_startup + +$PGPROTO -d $PGDATABASE -p $PGPOOL_PORT --test-v2 > result.txt 2>&1 +diff ../expected.txt result.txt +if [ $? = 0 ];then + echo "test Ok." + ./shutdownall + exit 0 +fi +echo "The expected result was different." +./shutdownall +exit 1 diff --git a/src/tools/pgproto/main.c b/src/tools/pgproto/main.c index 76e272a9a..43a361917 100644 --- a/src/tools/pgproto/main.c +++ b/src/tools/pgproto/main.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2017-2018 Tatsuo Ishii - * Copyright (c) 2018-2026 PgPool Global Development Group + * Portions Copyright (c) 2018-2026 PgPool Global Development Group + * Portions Copyright (c) 2017-2018 Tatsuo Ishii * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -23,6 +23,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include "pgproto/fe_memutils.h" #include #include "pgproto/read.h" @@ -40,6 +46,9 @@ static void read_and_process(FILE *fd, PGconn *conn); static int process_a_line(char *buf, PGconn *con); static int process_message_type(int kind, char *buf, PGconn *conn); static void process_function_call(char *buf, PGconn *conn); +static void connect_v2(char *host, char *port, char *user, char *database); +static int connect_unix_domain_socket_by_port(int port, char *socket_dir); +static int connect_inet_domain_socket_by_port(char *host, char *port); int read_nap = 0; @@ -58,6 +67,7 @@ main(int argc, char **argv) FILE *fd; PGconn *con; int var; + bool test_v2 = false; static struct option long_options[] = { {"host", optional_argument, NULL, 'h'}, @@ -69,6 +79,7 @@ main(int argc, char **argv) {"help", no_argument, NULL, '?'}, {"version", no_argument, NULL, 'v'}, {"read-nap", optional_argument, NULL, 'r'}, + {"test-v2", no_argument, NULL, 't'}, {NULL, 0, NULL, 0} }; @@ -81,7 +92,7 @@ main(int argc, char **argv) if ((env = getenv("PGUSER")) != NULL && *env != '\0') user = env; - while ((opt = getopt_long(argc, argv, "v?Dh:p:u:d:f:r:", long_options, &optindex)) != -1) + while ((opt = getopt_long(argc, argv, "v?Dh:p:u:d:f:r:t", long_options, &optindex)) != -1) { switch (opt) { @@ -123,12 +134,26 @@ main(int argc, char **argv) read_nap = atoi(optarg); break; + case 't': + test_v2 = true; + break; + default: usage(); exit(1); } } + if (test_v2) + { + /* + * test_v2 is specified. Send a v2 startup packet and see if it's + * actually rejected. We expect all existing (supported) backend + * rejects v2 protocol. Read the error message and show it. + */ + connect_v2(host, port, user, database); + exit(0); + } fd = openfile(data_file); con = connect_db(host, port, user, database); var = fcntl(PQsocket(con), F_GETFL, 0); @@ -168,6 +193,7 @@ usage(void) "-d, --database DATABASENAME (default: same as user)\n" "-f, --proto-data-file FILENAME (default: pgproto.data)\n" "-r, --read-nap NAPTIME (in micro seconds. default: 0)\n" + "-t, --test-v2\n" "-D, --debug\n" "-?, --help\n" "-v, --version\n", @@ -462,7 +488,7 @@ process_message_type(int kind, char *buf, PGconn *conn) process_function_call(buf, conn); break; - case 's': /* frontend sleep request */ + case 's': /* frontend sleep request */ SKIP_TABS(buf); stime = buffer_read_int(buf, &bufp); fprintf(stderr, "FE=> Sleep %ld\n", stime); @@ -622,3 +648,228 @@ process_function_call(char *buf, PGconn *conn) /* result format code */ send_int16(result_formatcode, conn); } + +/* + * connect_v2 + * + * Send a v2 startup packet and see if it's actually rejected. We expect all + * existing (supported) backend rejects v2 protocol. Read the error message + * and show it to stderr. + */ +static void +connect_v2(char *host, char *port, char *user, char *database) +{ + int fd = -1; + int port_val; + int v2_proto_version_h = 0x00020000; /* V2 protocol version in + * host order */ + int len; + int nlen; + char buf1[64]; + char buf2[32]; + char kind; + char *socket_dir = "/tmp"; + char *p; + + port_val = atoi(port); + if (port_val == 0) + { + fprintf(stderr, "invalid port \"%s\" specified\n", port); + exit(1); + } + + if (user == NULL || *user == '\0') + { + fprintf(stderr, "no user name specified\n"); + exit(1); + } + + if (database == NULL || *database == '\0') + database = strdup(user); + + if (host == NULL || *host == '\0' || *host == '/') + { + fd = connect_unix_domain_socket_by_port(port_val, socket_dir); + if (fd < 0) + exit(1); + } + else + { + fd = connect_inet_domain_socket_by_port(host, port); + } + + /* Create and send v2 protocol startup packet */ + len = sizeof(int); /* self lenghth */ + len += sizeof(int); /* protocol version */ + len += 64; /* database */ + len += 32; /* user */ + len += 64; /* options */ + len += 64; /* unused */ + len += 64; /* tty */ + + nlen = htonl(len); + write_it(fd, &nlen, sizeof(int)); /* message length including self */ + nlen = htonl(v2_proto_version_h); + write_it(fd, &nlen, sizeof(int)); /* protocol version */ + + memset(buf1, 0, sizeof(buf1)); + strlcpy(buf1, database, sizeof(buf1)); + write_it(fd, buf1, sizeof(buf1)); /* database */ + + memset(buf2, 0, sizeof(buf2)); + strlcpy(buf2, user, sizeof(buf2)); + write_it(fd, buf2, sizeof(buf2)); /* user */ + + memset(buf1, 0, sizeof(buf1)); + write_it(fd, buf1, sizeof(buf1)); /* options */ + + memset(buf1, 0, sizeof(buf1)); + write_it(fd, buf1, sizeof(buf1)); /* unused area */ + + memset(buf1, 0, sizeof(buf1)); + write_it(fd, buf1, sizeof(buf1)); /* tty */ + + /* read response from backend */ + read_internal(fd, &kind, sizeof(char), false); + if (kind == 'E') + fprintf(stderr, "<= BE ErrorResponse("); + else + { + fprintf(stderr, "unknown response %d\n", kind); + exit(1); + } + + len = 512; /* arbitrary value. expected this is enough. */ + p = pg_malloc0(len); + read_internal(fd, p, len, true); /* read data as much as possible */ + fprintf(stderr, "%s", p); /* print it */ + fprintf(stderr, ")\n"); + + exit(0); +} + +/* + * connect_unix_domain_socket_by_port + * + * Connect to PostgreSQL server by using UNIX domain socket. + */ +static int +connect_unix_domain_socket_by_port(int port, char *socket_dir) +{ + struct sockaddr_un addr; + int fd; + int len; + + if (socket_dir == NULL) + socket_dir = "/tmp"; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + fprintf(stderr, "failed to connect to PostgreSQL server by unix domain socket \"%m\""); + return -1; + } + + memset((char *) &addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.s.PGSQL.%d", socket_dir, port); + len = sizeof(struct sockaddr_un); + +retry: + if (connect(fd, (struct sockaddr *) &addr, len) < 0) + { + if (errno == EINTR || errno == EAGAIN) + goto retry; + close(fd); + fprintf(stderr, "failed to connect to PostgreSQL server by unix domain socket\n"); + fprintf(stderr, "connect to \"%s\" failed with error \"%m\"", addr.sun_path); + exit(1); + } + return fd; +} + +/* + * connect_inet_domain_socket_by_port + * + * Connect to PostgreSQL server by using INET domain socket. + */ +static int +connect_inet_domain_socket_by_port(char *host, char *port) +{ + int fd = -1; + int on = 1; + int ret; + struct addrinfo *res; + struct addrinfo *walk; + struct addrinfo hints; + int retry_cnt = 5; /* getaddrinfo() retry count in case EAI_AGAIN */ + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + for (;;) + { + if ((ret = getaddrinfo(host, port, &hints, &res)) != 0) + { + if (ret != EAI_AGAIN) + { + fprintf(stderr, "failed to connect to PostgreSQL server, getaddrinfo() failed with error \"%s\"\n", + gai_strerror(ret)); + exit(1); + } + retry_cnt--; + + if (retry_cnt <= 0) + { + fprintf(stderr, "failed to connect to PostgreSQL server, getaddrinfo() failed due to retry count over"); + exit(1); + } + } + else /* getaddrinfo succeeded. */ + break; + } + + for (walk = res; walk != NULL; walk = walk->ai_next) + { + fd = socket(walk->ai_family, walk->ai_socktype, walk->ai_protocol); + if (fd < 0) + { + fprintf(stderr, "failed to connect to PostgreSQL server, socket() failed \"%m\"\n"); + continue; + } + + /* set nodelay */ + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &on, + sizeof(on)) < 0) + { + fprintf(stderr, "failed to connect to PostgreSQL server, setsockopt() failed \"%m\"\n"); + close(fd); + freeaddrinfo(res); + exit(1); + } + +retry: + if (connect(fd, walk->ai_addr, walk->ai_addrlen) < 0) + { + if (errno == EINTR || errno == EAGAIN) + goto retry; + + else + { + fprintf(stderr, "failed to connect to PostgreSQL server on \"%s:%s\" \"%m\"\n", + host, port); + close(fd); + freeaddrinfo(res); + exit(1); + } + } + freeaddrinfo(res); + return fd; + } + fprintf(stderr, "failed to connect to PostgreSQL server\n"); + close(fd); + freeaddrinfo(res); + + exit(1); +} diff --git a/src/tools/pgproto/read.c b/src/tools/pgproto/read.c index 7dc44aa36..cbe25db03 100644 --- a/src/tools/pgproto/read.c +++ b/src/tools/pgproto/read.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2017-2018 Tatsuo Ishii - * Copyright (c) 2018-2023 PgPool Global Development Group + * Portions Copyright (c) 2018-2023 PgPool Global Development Group + * Portions Copyright (c) 2017-2018 Tatsuo Ishii * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -338,6 +338,15 @@ read_and_discard(PGconn *conn) */ static void read_it(PGconn *conn, char *buf, int len) +{ + read_internal(PQsocket(conn), buf, len, false); +} + +/* + * Wrapper for read(2). If noerr is true, do not raise an error at EOF. + */ +void +read_internal(int fd, char *buf, int len, bool noerr) { int sts; @@ -346,16 +355,21 @@ read_it(PGconn *conn, char *buf, int len) for (;;) { - sts = read(PQsocket(conn), buf, len); + sts = read(fd, buf, len); if (sts == 0) { - fprintf(stderr, "read_it: EOF detected"); - exit(1); + if (noerr) + return; + else + { + fprintf(stderr, "read_internal: EOF detected\n"); + exit(1); + } } else if (sts < 0) { - fprintf(stderr, "read_it: read(2) returns error %s\n", strerror(errno)); + fprintf(stderr, "read_internal: read(2) returns error %m\n"); exit(1); } diff --git a/src/tools/pgproto/send.c b/src/tools/pgproto/send.c index 2f359291b..77b2ab10b 100644 --- a/src/tools/pgproto/send.c +++ b/src/tools/pgproto/send.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2017-2018 Tatsuo Ishii - * Copyright (c) 2018-2021 PgPool Global Development Group + * Portions Copyright (c) 2018-2026 PgPool Global Development Group + * Portions Copyright (c) 2017-2018 Tatsuo Ishii * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -29,8 +29,6 @@ #include "pgproto/read.h" #include "pgproto/send.h" -static void write_it(int fd, void *buf, int len); - /* * Send a character to the connection. */ @@ -81,18 +79,16 @@ send_byte(char *buf, int len, PGconn *conn) } /* - * Wrapper for write(2). + * Wrapper for write(2) + * + * If write() fails, print an error message and exit. */ -static void write_it(int fd, void *buf, int len) { - int errsave = errno; - - errno = 0; if (write(fd, buf, len) < 0) { - fprintf(stderr, "write_it: warning write(2) failed: %s\n", strerror(errno)); + fprintf(stderr, "write_it: warning: write(2) failed: \"%m\"\n"); + exit(1); } - errno = errsave; } -- 2.43.0