From 82230d3f76711dd236cd3e50b6baf078552807b0 Mon Sep 17 00:00:00 2001 From: Yurii Rashkovskii Date: Wed, 29 Mar 2023 11:54:50 +0700 Subject: [PATCH] Allow listening port to be 0 This makes it possible to start postgres on an unused port instead of trying to reliably pick it in cases where this is desirable, such as test benches or colocated database instances. This is done by retrieving the assigned port number using `getsockname`. Those using this feature can retrieve the assigned port through postmaster.pid --- doc/src/sgml/config.sgml | 4 ++ src/backend/libpq/pqcomm.c | 77 +++++++++++++++++++++++++++-- src/backend/postmaster/postmaster.c | 8 +-- src/backend/utils/misc/guc_tables.c | 2 +- src/include/libpq/libpq.h | 3 ++ 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index bda0da2dc8..84be4e33c7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -682,6 +682,10 @@ include_dir 'conf.d' same port number is used for all IP addresses the server listens on. This parameter can only be set at server start. + + The port can be set to 0 to make Postgres pick an unused port number. + The assigned port number can be then retrieved from postmaster.pid. + diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index da5bb5fc5d..f713437ce4 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -302,11 +302,12 @@ socket_close(int code, Datum arg) * Stream functions are used for vanilla TCP connection protocol. */ - /* - * StreamServerPort -- open a "listening" port to accept connections. + * StreamServerPortReturn -- open a "listening" port to accept connections. + * + * family should be AF_UNIX or AF_UNSPEC; portNumberPtr is a non-NULL pointer + * to a port number. * - * family should be AF_UNIX or AF_UNSPEC; portNumber is the port number. * For AF_UNIX ports, hostName should be NULL and unixSocketDir must be * specified. For TCP ports, hostName is either NULL for all interfaces or * the interface to listen on, and unixSocketDir is ignored (can be NULL). @@ -314,13 +315,20 @@ socket_close(int code, Datum arg) * Successfully opened sockets are added to the ListenSocket[] array (of * length MaxListen), at the first position that isn't PGINVALID_SOCKET. * + * This function allows retrieving assigned port number through portNumberPtr + * (if the original value of *portNumberPtr is 0, signifying an unused port + * selection) + * + * If the retrieval of the assigned port number is not necessary, StreamServerPort + * can be used instead. + * * RETURNS: STATUS_OK or STATUS_ERROR */ - int -StreamServerPort(int family, const char *hostName, unsigned short portNumber, +StreamServerPortReturn(int family, const char *hostName, unsigned short *portNumberPtr, const char *unixSocketDir, pgsocket ListenSocket[], int MaxListen) + { pgsocket fd; int err; @@ -342,6 +350,11 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber, int one = 1; #endif + /* portNumberPtr must contain a value */ + Assert(portNumberPtr != NULL); + unsigned short portNumber = *portNumberPtr; + + /* Initialize hint structure */ MemSet(&hint, 0, sizeof(hint)); hint.ai_family = family; @@ -539,6 +552,39 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber, closesocket(fd); break; } + } else if (portNumber == 0) + { + /* Return actual, effective portNumber through portNumberPtr. + * + * The reason it works uniformly across different socket types is because + * port number is updated in a sequence and on the following iterations, it will + * be no longer set to 0. Therefore, all sockets will share the same IP address. + * + * If it is impossible to listen on a socket on the port that was unused in one family, + * it will result in an error. + */ + struct sockaddr_in sockaddr; + StaticAssertStmt(offsetof(struct sockaddr_in, sin_port) == offsetof(struct sockaddr_in6, sin6_port), + "sockaddr_in and sockaddr_in6 must have port at the same offset"); + socklen_t socksize = sizeof(sockaddr); + + if (getsockname(fd, (struct sockaddr *)&sockaddr, &socksize) == -1) + { + int saved_errno = errno; + ereport(LOG, errmsg("getsockname failed with: %s", strerror(saved_errno))); + closesocket(fd); + break; + } + + if (addr->ai_family == AF_INET) + { + *portNumberPtr = ntohs(sockaddr.sin_port); + } else if (addr->ai_family == AF_INET6) + { + *portNumberPtr = ntohs(((struct sockaddr_in6 *)&sockaddr)->sin6_port); + } + + portNumber = *portNumberPtr; } /* @@ -582,6 +628,27 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber, return STATUS_OK; } +/* + * StreamServerPort -- open a "listening" port to accept connections. + * + * family should be AF_UNIX or AF_UNSPEC; portNumber is the port number. + * For AF_UNIX ports, hostName should be NULL and unixSocketDir must be + * specified. For TCP ports, hostName is either NULL for all interfaces or + * the interface to listen on, and unixSocketDir is ignored (can be NULL). + * + * Successfully opened sockets are added to the ListenSocket[] array (of + * length MaxListen), at the first position that isn't PGINVALID_SOCKET. + * + * RETURNS: STATUS_OK or STATUS_ERROR + */ + +int +StreamServerPort(int family, const char *hostName, unsigned short portNumber, + const char *unixSocketDir, + pgsocket ListenSocket[], int MaxListen) +{ + return StreamServerPortReturn(family, hostName, &portNumber, unixSocketDir, ListenSocket, MaxListen); +} /* * Lock_AF_UNIX -- configure unix socket file path diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 4c49393fc5..1e4029783c 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1211,13 +1211,13 @@ PostmasterMain(int argc, char *argv[]) char *curhost = (char *) lfirst(l); if (strcmp(curhost, "*") == 0) - status = StreamServerPort(AF_UNSPEC, NULL, - (unsigned short) PostPortNumber, + status = StreamServerPortReturn(AF_UNSPEC, NULL, + (unsigned short *) &PostPortNumber, NULL, ListenSocket, MAXLISTEN); else - status = StreamServerPort(AF_UNSPEC, curhost, - (unsigned short) PostPortNumber, + status = StreamServerPortReturn(AF_UNSPEC, curhost, + (unsigned short *) &PostPortNumber, NULL, ListenSocket, MAXLISTEN); diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 8062589efd..11b7c3d46f 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2263,7 +2263,7 @@ struct config_int ConfigureNamesInt[] = NULL }, &PostPortNumber, - DEF_PGPORT, 1, 65535, + DEF_PGPORT, 0, 65535, NULL, NULL, NULL }, diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 50fc781f47..c875da55e2 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -67,6 +67,9 @@ extern PGDLLIMPORT WaitEventSet *FeBeWaitSet; extern int StreamServerPort(int family, const char *hostName, unsigned short portNumber, const char *unixSocketDir, pgsocket ListenSocket[], int MaxListen); +extern int StreamServerPortReturn(int family, const char *hostName, + unsigned short *portNumberPtr, const char *unixSocketDir, + pgsocket ListenSocket[], int MaxListen); extern int StreamConnection(pgsocket server_fd, Port *port); extern void StreamClose(pgsocket sock); extern void TouchSocketFiles(void); -- 2.37.1 (Apple Git-137.1)