diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index b420486a0a..5d8fcc3d50 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -353,6 +353,15 @@ hostnogssenc database user
+
+ If is enabled and the
+ connection is made through a proxy server using the PROXY
+ protocol, the actual IP address of the client will be used
+ for matching. If a connection is made through a proxy server
+ not using the PROXY protocol, the IP address of the
+ proxy server will be used.
+
+
These fields do not apply to local records.
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 967de73596..e1999bfeb5 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -682,6 +682,56 @@ include_dir 'conf.d'
+
+ proxy_port (integer)
+
+ proxy_port configuration parameter
+
+
+
+
+ The TCP port the server listens on for PROXY connections, disabled by
+ default. If set to a number, PostgreSQL
+ will listen on this port on the same addresses as for regular
+ connections, but expect all connections to use the PROXY protocol to
+ identify the client. This parameter can only be set at server start.
+
+
+ If a proxy connection is done over this port, and the proxy is listed
+ in , the actual client address
+ will be considered as the address of the client, instead of listing
+ all connections as coming from the proxy server.
+
+
+ The PROXY
+ protocol is maintained by HAProxy,
+ and supported in many proxies and load
+ balancers. PostgreSQL supports version 2
+ of the protocol.
+
+
+
+
+
+ proxy_servers (string)
+
+ proxy_servers configuration parameter
+
+
+
+
+ A comma separated list of one or more host names, cidr specifications or the
+ literal unix, indicating which proxy servers to trust when
+ connecting on the port specified in .
+
+
+ If a proxy connection is made from an IP address not covered by this
+ list, the connection will be rejected. By default no proxy is trusted
+ and all proxy connections will be rejected.
+
+
+
+
max_connections (integer)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 994251e7d9..470e36db58 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1761,6 +1761,14 @@ ident_inet(hbaPort *port)
*la = NULL,
hints;
+ if (port->isProxy)
+ {
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("Ident authentication cannot be used over PROXY connections")));
+ return STATUS_ERROR;
+ }
+
/*
* Might look a little weird to first convert it to text and then back to
* sockaddr, but it's protocol independent.
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 4c7b1e7bfd..9f92195097 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -308,13 +308,13 @@ 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.
*
- * RETURNS: STATUS_OK or STATUS_ERROR
+ * RETURNS: The PQlistenSocket listening on, or NULL in case of error
*/
-int
+PQlistenSocket *
StreamServerPort(int family, const char *hostName, unsigned short portNumber,
const char *unixSocketDir,
- pgsocket ListenSocket[], int MaxListen)
+ PQlistenSocket ListenSocket[], int MaxListen)
{
pgsocket fd;
int err;
@@ -359,10 +359,10 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
(errmsg("Unix-domain socket path \"%s\" is too long (maximum %d bytes)",
unixSocketPath,
(int) (UNIXSOCK_PATH_BUFLEN - 1))));
- return STATUS_ERROR;
+ return NULL;
}
if (Lock_AF_UNIX(unixSocketDir, unixSocketPath) != STATUS_OK)
- return STATUS_ERROR;
+ return NULL;
service = unixSocketPath;
}
else
@@ -385,7 +385,7 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
service, gai_strerror(ret))));
if (addrs)
pg_freeaddrinfo_all(hint.ai_family, addrs);
- return STATUS_ERROR;
+ return NULL;
}
for (addr = addrs; addr; addr = addr->ai_next)
@@ -402,7 +402,7 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
/* See if there is still room to add 1 more socket. */
for (; listen_index < MaxListen; listen_index++)
{
- if (ListenSocket[listen_index] == PGINVALID_SOCKET)
+ if (ListenSocket[listen_index].socket == PGINVALID_SOCKET)
break;
}
if (listen_index >= MaxListen)
@@ -579,16 +579,16 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
(errmsg("listening on %s address \"%s\", port %d",
familyDesc, addrDesc, (int) portNumber)));
- ListenSocket[listen_index] = fd;
+ ListenSocket[listen_index].socket = fd;
added++;
}
pg_freeaddrinfo_all(hint.ai_family, addrs);
if (!added)
- return STATUS_ERROR;
+ return NULL;
- return STATUS_OK;
+ return &ListenSocket[listen_index];
}
@@ -1113,7 +1113,7 @@ pq_getbytes(char *s, size_t len)
* returns 0 if OK, EOF if trouble
* --------------------------------
*/
-static int
+int
pq_discardbytes(size_t len)
{
size_t amount;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index edab95a19e..0344f907c9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -102,6 +102,7 @@
#include "common/string.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
+#include "libpq/ifaddr.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
@@ -195,15 +196,22 @@ BackgroundWorker *MyBgworkerEntry = NULL;
-/* The socket number we are listening for connections on */
+/* The TCP port number we are listening for connections on */
int PostPortNumber;
+/* The TCP port number we are listening for proxy connections on */
+int ProxyPortNumber;
+
/* The directory names for Unix socket(s) */
char *Unix_socket_directories;
/* The TCP listen address(es) */
char *ListenAddresses;
+/* Trusted proxy servers */
+char *TrustedProxyServersString = NULL;
+struct sockaddr_storage *TrustedProxyServers = NULL;
+
/*
* ReservedBackends is the number of backends reserved for superuser use.
* This number is taken out of the pool size given by MaxConnections so
@@ -217,7 +225,7 @@ int ReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
-static pgsocket ListenSocket[MAXLISTEN];
+static PQlistenSocket ListenSocket[MAXLISTEN];
/*
* These globals control the behavior of the postmaster in case some
@@ -581,6 +589,7 @@ PostmasterMain(int argc, char *argv[])
bool listen_addr_saved = false;
int i;
char *output_config_variable = NULL;
+ PQlistenSocket *socket = NULL;
InitProcessGlobals();
@@ -1124,7 +1133,10 @@ PostmasterMain(int argc, char *argv[])
* charged with closing the sockets again at postmaster shutdown.
*/
for (i = 0; i < MAXLISTEN; i++)
- ListenSocket[i] = PGINVALID_SOCKET;
+ {
+ ListenSocket[i].socket = PGINVALID_SOCKET;
+ ListenSocket[i].isProxy = false;
+ }
on_proc_exit(CloseServerPorts, 0);
@@ -1153,17 +1165,17 @@ PostmasterMain(int argc, char *argv[])
char *curhost = (char *) lfirst(l);
if (strcmp(curhost, "*") == 0)
- status = StreamServerPort(AF_UNSPEC, NULL,
+ socket = StreamServerPort(AF_UNSPEC, NULL,
(unsigned short) PostPortNumber,
NULL,
ListenSocket, MAXLISTEN);
else
- status = StreamServerPort(AF_UNSPEC, curhost,
+ socket = StreamServerPort(AF_UNSPEC, curhost,
(unsigned short) PostPortNumber,
NULL,
ListenSocket, MAXLISTEN);
- if (status == STATUS_OK)
+ if (socket)
{
success++;
/* record the first successful host addr in lockfile */
@@ -1177,9 +1189,30 @@ PostmasterMain(int argc, char *argv[])
ereport(WARNING,
(errmsg("could not create listen socket for \"%s\"",
curhost)));
+
+ /* Also listen to the PROXY port on this address, if configured */
+ if (ProxyPortNumber)
+ {
+ if (strcmp(curhost, "*") == 0)
+ socket = StreamServerPort(AF_UNSPEC, NULL,
+ (unsigned short) ProxyPortNumber,
+ NULL,
+ ListenSocket, MAXLISTEN);
+ else
+ socket = StreamServerPort(AF_UNSPEC, curhost,
+ (unsigned short) ProxyPortNumber,
+ NULL,
+ ListenSocket, MAXLISTEN);
+ if (socket)
+ socket->isProxy = true;
+ else
+ ereport(WARNING,
+ (errmsg("could not create PROXY listen socket for \"%s\"",
+ curhost)));
+ }
}
- if (!success && elemlist != NIL)
+ if (socket == NULL && elemlist != NIL)
ereport(FATAL,
(errmsg("could not create any TCP/IP sockets")));
@@ -1189,7 +1222,7 @@ PostmasterMain(int argc, char *argv[])
#ifdef USE_BONJOUR
/* Register for Bonjour only if we opened TCP socket(s) */
- if (enable_bonjour && ListenSocket[0] != PGINVALID_SOCKET)
+ if (enable_bonjour && ListenSocket[0].socket != PGINVALID_SOCKET)
{
DNSServiceErrorType err;
@@ -1251,12 +1284,12 @@ PostmasterMain(int argc, char *argv[])
{
char *socketdir = (char *) lfirst(l);
- status = StreamServerPort(AF_UNIX, NULL,
+ socket = StreamServerPort(AF_UNIX, NULL,
(unsigned short) PostPortNumber,
socketdir,
ListenSocket, MAXLISTEN);
- if (status == STATUS_OK)
+ if (socket)
{
success++;
/* record the first successful Unix socket in lockfile */
@@ -1267,9 +1300,23 @@ PostmasterMain(int argc, char *argv[])
ereport(WARNING,
(errmsg("could not create Unix-domain socket in directory \"%s\"",
socketdir)));
+
+ if (ProxyPortNumber)
+ {
+ socket = StreamServerPort(AF_UNIX, NULL,
+ (unsigned short) ProxyPortNumber,
+ socketdir,
+ ListenSocket, MAXLISTEN);
+ if (socket)
+ socket->isProxy = true;
+ else
+ ereport(WARNING,
+ (errmsg("could not create Unix-domain PROXY socket for \"%s\"",
+ socketdir)));
+ }
}
- if (!success && elemlist != NIL)
+ if (socket == NULL && elemlist != NIL)
ereport(FATAL,
(errmsg("could not create any Unix-domain sockets")));
@@ -1281,7 +1328,7 @@ PostmasterMain(int argc, char *argv[])
/*
* check that we have some socket to listen on
*/
- if (ListenSocket[0] == PGINVALID_SOCKET)
+ if (ListenSocket[0].socket == PGINVALID_SOCKET)
ereport(FATAL,
(errmsg("no socket created for listening")));
@@ -1430,10 +1477,10 @@ CloseServerPorts(int status, Datum arg)
*/
for (i = 0; i < MAXLISTEN; i++)
{
- if (ListenSocket[i] != PGINVALID_SOCKET)
+ if (ListenSocket[i].socket != PGINVALID_SOCKET)
{
- StreamClose(ListenSocket[i]);
- ListenSocket[i] = PGINVALID_SOCKET;
+ StreamClose(ListenSocket[i].socket);
+ ListenSocket[i].socket = PGINVALID_SOCKET;
}
}
@@ -1722,15 +1769,17 @@ ServerLoop(void)
for (i = 0; i < MAXLISTEN; i++)
{
- if (ListenSocket[i] == PGINVALID_SOCKET)
+ if (ListenSocket[i].socket == PGINVALID_SOCKET)
break;
- if (FD_ISSET(ListenSocket[i], &rmask))
+ if (FD_ISSET(ListenSocket[i].socket, &rmask))
{
Port *port;
- port = ConnCreate(ListenSocket[i]);
+ port = ConnCreate(ListenSocket[i].socket);
if (port)
{
+ port->isProxy = ListenSocket[i].isProxy;
+
BackendStartup(port);
/*
@@ -1898,7 +1947,7 @@ initMasks(fd_set *rmask)
for (i = 0; i < MAXLISTEN; i++)
{
- int fd = ListenSocket[i];
+ int fd = ListenSocket[i].socket;
if (fd == PGINVALID_SOCKET)
break;
@@ -1911,6 +1960,213 @@ initMasks(fd_set *rmask)
return maxsock + 1;
}
+static int
+UnwrapProxyConnection(Port *port)
+{
+ char proxyver;
+ uint16 proxyaddrlen;
+ SockAddr raddr_save;
+ int i;
+ bool useproxy = false;
+
+ /*
+ * These structs are from the PROXY protocol docs at
+ * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
+ */
+ union
+ {
+ struct
+ { /* for TCP/UDP over IPv4, len = 12 */
+ uint32 src_addr;
+ uint32 dst_addr;
+ uint16 src_port;
+ uint16 dst_port;
+ } ip4;
+ struct
+ { /* for TCP/UDP over IPv6, len = 36 */
+ uint8 src_addr[16];
+ uint8 dst_addr[16];
+ uint16 src_port;
+ uint16 dst_port;
+ } ip6;
+ } proxyaddr;
+ struct
+ {
+ uint8 sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ uint8 ver_cmd; /* protocol version and command */
+ uint8 fam; /* protocol family and address */
+ uint16 len; /* number of following bytes part of the
+ * header */
+ } proxyheader;
+
+
+ /* Else if it's on our list of trusted proxies */
+ if (TrustedProxyServers)
+ {
+ for (i = 0; i < *((int *) TrustedProxyServers) * 2; i += 2)
+ {
+ if (port->raddr.addr.ss_family == TrustedProxyServers[i + 1].ss_family)
+ {
+ /*
+ * Connection over unix sockets don't give us the source, so
+ * just check if they're allowed at all. For IP connections,
+ * verify that it's an allowed address.
+ */
+ if (port->raddr.addr.ss_family == AF_UNIX ||
+ pg_range_sockaddr(&port->raddr.addr,
+ &TrustedProxyServers[i + 1],
+ &TrustedProxyServers[i + 2]))
+ {
+ useproxy = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!useproxy)
+ {
+ /*
+ * Connection is not from one of our trusted proxies, so reject it.
+ */
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("connection from unauthorized proxy server")));
+ return STATUS_ERROR;
+ }
+
+ /* Store a copy of the original address, for logging */
+ memcpy(&raddr_save, &port->raddr, sizeof(SockAddr));
+
+ pq_startmsgread();
+
+ /*
+ * PROXY requests always start with:
+ * \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A
+ */
+
+ if (pq_getbytes((char *) &proxyheader, sizeof(proxyheader)) != 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+
+ if (memcmp(proxyheader.sig, "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a", sizeof(proxyheader.sig)) != 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy packet")));
+ return STATUS_ERROR;
+ }
+
+ /* Proxy version is in the high 4 bits of the first byte */
+ proxyver = (proxyheader.ver_cmd & 0xF0) >> 4;
+ if (proxyver != 2)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol version: %x", proxyver)));
+ return STATUS_ERROR;
+ }
+
+ proxyaddrlen = pg_ntoh16(proxyheader.len);
+
+ if (pq_getbytes((char *) &proxyaddr, proxyaddrlen > sizeof(proxyaddr) ? sizeof(proxyaddr) : proxyaddrlen) == EOF)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+
+ /* Lower 4 bits hold type of connection */
+ if (proxyheader.fam == 0)
+ {
+ /* LOCAL connection, so we ignore the address included */
+ }
+ else if (proxyheader.fam == 0x11)
+ {
+ /* TCPv4 */
+ if (proxyaddrlen < 12)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+ port->raddr.addr.ss_family = AF_INET;
+ port->raddr.salen = sizeof(struct sockaddr_in);
+ ((struct sockaddr_in *) &port->raddr.addr)->sin_addr.s_addr = proxyaddr.ip4.src_addr;
+ ((struct sockaddr_in *) &port->raddr.addr)->sin_port = proxyaddr.ip4.src_port;
+ }
+ else if (proxyheader.fam == 0x21)
+ {
+ /* TCPv6 */
+ if (proxyaddrlen < 36)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+ port->raddr.addr.ss_family = AF_INET6;
+ port->raddr.salen = sizeof(struct sockaddr_in6);
+ memcpy(&((struct sockaddr_in6 *) &port->raddr.addr)->sin6_addr, proxyaddr.ip6.src_addr, 16);
+ ((struct sockaddr_in6 *) &port->raddr.addr)->sin6_port = proxyaddr.ip6.src_port;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol connection type: %x", proxyheader.fam)));
+ return STATUS_ERROR;
+ }
+
+ /* If there is any more header data present, skip past it */
+ if (proxyaddrlen > sizeof(proxyaddr))
+ {
+ if (pq_discardbytes(proxyaddrlen - sizeof(proxyaddr)) == EOF)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+ }
+
+ pq_endmsgread();
+
+ /*
+ * Log what we've done if connection logging is enabled. We log the proxy
+ * connection here, and let the normal connection logging mechanism log
+ * the unwrapped connection.
+ */
+ if (Log_connections)
+ {
+ char remote_host[NI_MAXHOST];
+ char remote_port[NI_MAXSERV];
+ int ret;
+
+ remote_host[0] = '\0';
+ remote_port[0] = '\0';
+ if ((ret = pg_getnameinfo_all(&raddr_save.addr, raddr_save.salen,
+ remote_host, sizeof(remote_host),
+ remote_port, sizeof(remote_port),
+ (log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0)
+ ereport(WARNING,
+ (errmsg_internal("pg_getnameinfo_all() failed: %s",
+ gai_strerror(ret))));
+
+ ereport(LOG,
+ (errmsg("proxy connection from: host=%s port=%s",
+ remote_host,
+ remote_port)));
+
+ }
+
+ return STATUS_OK;
+}
/*
* Read a client's startup packet and do something according to it.
@@ -2019,7 +2275,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
#ifdef USE_SSL
/* No SSL when disabled or on Unix sockets */
- if (!LoadedSSL || IS_AF_UNIX(port->laddr.addr.ss_family))
+ if (!LoadedSSL || (IS_AF_UNIX(port->laddr.addr.ss_family) && !port->isProxy))
SSLok = 'N';
else
SSLok = 'S'; /* Support for SSL */
@@ -2056,7 +2312,7 @@ retry1:
#ifdef ENABLE_GSS
/* No GSSAPI encryption when on Unix socket */
- if (!IS_AF_UNIX(port->laddr.addr.ss_family))
+ if (!IS_AF_UNIX(port->laddr.addr.ss_family) || port->isProxy)
GSSok = 'G';
#endif
@@ -2555,10 +2811,10 @@ ClosePostmasterPorts(bool am_syslogger)
*/
for (i = 0; i < MAXLISTEN; i++)
{
- if (ListenSocket[i] != PGINVALID_SOCKET)
+ if (ListenSocket[i].socket != PGINVALID_SOCKET)
{
- StreamClose(ListenSocket[i]);
- ListenSocket[i] = PGINVALID_SOCKET;
+ StreamClose(ListenSocket[i].socket);
+ ListenSocket[i].socket = PGINVALID_SOCKET;
}
}
@@ -4320,6 +4576,33 @@ BackendInitialize(Port *port)
InitializeTimeouts(); /* establishes SIGALRM handler */
PG_SETMASK(&StartupBlockSig);
+ /*
+ * Ready to begin client interaction. We will give up and _exit(1) after
+ * a time delay, so that a broken client can't hog a connection
+ * indefinitely. PreAuthDelay and any DNS interactions above don't count
+ * against the time limit.
+ *
+ * Note: AuthenticationTimeout is applied here while waiting for the
+ * startup packet, and then again in InitPostgres for the duration of any
+ * authentication operations. So a hostile client could tie up the
+ * process for nearly twice AuthenticationTimeout before we kick him off.
+ *
+ * Note: because PostgresMain will call InitializeTimeouts again, the
+ * registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay
+ * since we never use it again after this function.
+ */
+ RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
+
+ /* Check if this is a proxy connection and if so unwrap the proxying */
+ if (port->isProxy)
+ {
+ enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
+ if (UnwrapProxyConnection(port) != STATUS_OK)
+ proc_exit(0);
+ disable_timeout(STARTUP_PACKET_TIMEOUT, false);
+ }
+
+
/*
* Get the remote host name and port for logging and status display.
*/
@@ -4371,28 +4654,11 @@ BackendInitialize(Port *port)
strspn(remote_host, "0123456789ABCDEFabcdef:") < strlen(remote_host))
port->remote_hostname = strdup(remote_host);
- /*
- * Ready to begin client interaction. We will give up and _exit(1) after
- * a time delay, so that a broken client can't hog a connection
- * indefinitely. PreAuthDelay and any DNS interactions above don't count
- * against the time limit.
- *
- * Note: AuthenticationTimeout is applied here while waiting for the
- * startup packet, and then again in InitPostgres for the duration of any
- * authentication operations. So a hostile client could tie up the
- * process for nearly twice AuthenticationTimeout before we kick him off.
- *
- * Note: because PostgresMain will call InitializeTimeouts again, the
- * registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay
- * since we never use it again after this function.
- */
- RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
- enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
-
/*
* Receive the startup packet (which might turn out to be a cancel request
* packet).
*/
+ enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
status = ProcessStartupPacket(port, false, false);
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3fd1a5fbe2..8b3be40a26 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -46,10 +46,12 @@
#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/variable.h"
+#include "common/ip.h"
#include "common/string.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "libpq/auth.h"
+#include "libpq/ifaddr.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
@@ -227,6 +229,8 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou
static void assign_recovery_target_lsn(const char *newval, void *extra);
static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
+static bool check_proxy_servers(char **newval, void **extra, GucSource source);
+static void assign_proxy_servers(const char *newval, void *extra);
/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2291,6 +2295,16 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"proxy_port", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the TCP port the server listens for PROXY connections on."),
+ NULL
+ },
+ &ProxyPortNumber,
+ 0, 0, 65535,
+ NULL, NULL, NULL
+ },
+
{
{"unix_socket_permissions", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
gettext_noop("Sets the access permissions of the Unix-domain socket."),
@@ -4242,6 +4256,17 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"proxy_servers", PGC_SIGHUP, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the addresses for trusted proxy servers."),
+ NULL,
+ GUC_LIST_INPUT
+ },
+ &TrustedProxyServersString,
+ "",
+ check_proxy_servers, assign_proxy_servers, NULL
+ },
+
{
/*
* Can't be set by ALTER SYSTEM as it can lead to recursive definition
@@ -12227,4 +12252,118 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
return true;
}
+static bool
+check_proxy_servers(char **newval, void **extra, GucSource source)
+{
+ char *rawstring;
+ List *elemlist;
+ ListCell *l;
+ struct sockaddr_storage *myextra;
+
+ /* Special case when it's empty */
+ if (**newval == '\0')
+ {
+ *extra = NULL;
+ return true;
+ }
+
+ /* Need a modifiable copy of string */
+ rawstring = pstrdup(*newval);
+
+ /* Parse string into list of identifiers */
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ GUC_check_errdetail("List syntax is invalid.");
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ if (list_length(elemlist) == 0)
+ {
+ /* If it had only whitespace */
+ pfree(rawstring);
+ list_free(elemlist);
+
+ *extra = NULL;
+ return true;
+ }
+
+ /*
+ * We store the result in an array of sockaddr_storage. The first entry is
+ * just an overloaded int which holds the size of the array.
+ */
+ myextra = (struct sockaddr_storage *) guc_malloc(ERROR, sizeof(struct sockaddr_storage) * (list_length(elemlist) * 2 + 1));
+ *((int *) &myextra[0]) = list_length(elemlist);
+
+ foreach(l, elemlist)
+ {
+ char *tok = (char *) lfirst(l);
+ char *netmasktok = NULL;
+ int ret;
+ struct addrinfo *gai_result;
+ struct addrinfo hints;
+
+ /*
+ * Unix sockets don't have endpoint addresses, so just flag them as
+ * AF_UNIX
+ */
+ if (pg_strcasecmp(tok, "unix") == 0)
+ {
+ myextra[foreach_current_index(l) * 2 + 1].ss_family = AF_UNIX;
+ continue;
+ }
+
+ netmasktok = strchr(tok, '/');
+ if (netmasktok)
+ {
+ *netmasktok = '\0';
+ netmasktok++;
+ }
+
+ memset((char *) &hints, 0, sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+
+ ret = pg_getaddrinfo_all(tok, NULL, &hints, &gai_result);
+ if (ret != 0 || gai_result == NULL)
+ {
+ GUC_check_errdetail("Invalid IP addrress %s", tok);
+ pfree(rawstring);
+ list_free(elemlist);
+ free(myextra);
+ return false;
+ }
+
+ memcpy((char *) &myextra[foreach_current_index(l) * 2 + 1], gai_result->ai_addr, gai_result->ai_addrlen);
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+
+ /* A NULL netmasktok means the fully set hostmask */
+ if (pg_sockaddr_cidr_mask(&myextra[foreach_current_index(l) * 2 + 2], netmasktok, myextra[foreach_current_index(l) * 2 + 1].ss_family) != 0)
+ {
+ if (netmasktok)
+ GUC_check_errdetail("Invalid netmask %s", netmasktok);
+ else
+ GUC_check_errdetail("Could not create netmask");
+ pfree(rawstring);
+ list_free(elemlist);
+ free(myextra);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+assign_proxy_servers(const char *newval, void *extra)
+{
+ TrustedProxyServers = (struct sockaddr_storage *) extra;
+}
+
#include "guc-file.c"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee06528bb0..b002228393 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -61,6 +61,9 @@
# defaults to 'localhost'; use '*' for all
# (change requires restart)
#port = 5432 # (change requires restart)
+#proxy_port = 0 # port to listen to for proxy connections
+ # (change requires restart)
+#proxy_servers = '' # what proxy servers to trust
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..57edda122a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -121,6 +121,7 @@ typedef struct Port
{
pgsocket sock; /* File descriptor */
bool noblock; /* is the socket in non-blocking mode? */
+ bool isProxy; /* is the connection using PROXY protocol */
ProtocolVersion proto; /* FE/BE protocol version */
SockAddr laddr; /* local addr (postmaster) */
SockAddr raddr; /* remote addr (client) */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b20deeb555..c06ee29f88 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -33,6 +33,12 @@ typedef struct
extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
+typedef struct
+{
+ pgsocket socket;
+ bool isProxy;
+} PQlistenSocket;
+
#define pq_comm_reset() (PqCommMethods->comm_reset())
#define pq_flush() (PqCommMethods->flush())
#define pq_flush_if_writable() (PqCommMethods->flush_if_writable())
@@ -54,9 +60,9 @@ extern WaitEventSet *FeBeWaitSet;
#define FeBeWaitSetSocketPos 0
#define FeBeWaitSetLatchPos 1
-extern int StreamServerPort(int family, const char *hostName,
- unsigned short portNumber, const char *unixSocketDir,
- pgsocket ListenSocket[], int MaxListen);
+extern PQlistenSocket *StreamServerPort(int family, const char *hostName,
+ unsigned short portNumber, const char *unixSocketDir,
+ PQlistenSocket PQlistenSocket[], int MaxListen);
extern int StreamConnection(pgsocket server_fd, Port *port);
extern void StreamClose(pgsocket sock);
extern void TouchSocketFiles(void);
@@ -69,6 +75,7 @@ extern bool pq_is_reading_msg(void);
extern int pq_getmessage(StringInfo s, int maxlen);
extern int pq_getbyte(void);
extern int pq_peekbyte(void);
+extern int pq_discardbytes(size_t len);
extern int pq_getbyte_if_available(unsigned char *c);
extern int pq_putmessage_v2(char msgtype, const char *s, size_t len);
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index cfa59c4dc0..9ed219dfda 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -17,10 +17,13 @@
extern bool EnableSSL;
extern int ReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
+extern PGDLLIMPORT int ProxyPortNumber;
extern int Unix_socket_permissions;
extern char *Unix_socket_group;
extern char *Unix_socket_directories;
extern char *ListenAddresses;
+extern char *TrustedProxyServersString;
+extern struct sockaddr_storage *TrustedProxyServers;
extern bool ClientAuthInProgress;
extern int PreAuthDelay;
extern int AuthenticationTimeout;
diff --git a/src/test/Makefile b/src/test/Makefile
index f7859c2fd5..cfb9a319a2 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../..
include $(top_builddir)/src/Makefile.global
SUBDIRS = perl regress isolation modules authentication recovery subscription \
- locale
+ locale protocol
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
diff --git a/src/test/protocol/Makefile b/src/test/protocol/Makefile
new file mode 100644
index 0000000000..bda49d6ecb
--- /dev/null
+++ b/src/test/protocol/Makefile
@@ -0,0 +1,23 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/protocol
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/protocol/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/protocol
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/protocol/t/001_proxy.pl b/src/test/protocol/t/001_proxy.pl
new file mode 100644
index 0000000000..c84d797e4f
--- /dev/null
+++ b/src/test/protocol/t/001_proxy.pl
@@ -0,0 +1,157 @@
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More;
+use Socket qw(AF_INET AF_INET6 inet_pton);
+use IO::Socket;
+
+plan tests => 35;
+
+my $node = get_new_node('node');
+$node->init;
+$node->append_conf(
+ 'postgresql.conf', qq{
+listen_addresses = 'localhost'
+log_connections = on
+});
+$node->append_conf(
+ 'pg_hba.conf', qq{
+host all all 11.22.33.44/32 trust
+host all all 1:2:3:4:5:6:0:9/128 trust
+});
+$node->append_conf('postgresql.conf', "proxy_port = " . ($node->port() + 1));
+
+$node->start;
+
+sub make_message
+{
+ my ($msg) = @_;
+ return pack("Na*", length($msg) + 4, $msg);
+}
+
+sub read_packet
+{
+ my ($socket) = @_;
+ my $buf = "";
+ $socket->recv($buf, 1024);
+ return $buf;
+}
+
+
+# Test normal connection through localhost
+sub test_connection
+{
+ my ($socket, $proxy, $what, $shouldbe, $shouldfail, $extra) = @_;
+ ok($socket, $what);
+
+ my $startup = make_message(
+ pack("N(Z*Z*)*x", 196608, (user => "mha", database => "postgres")));
+
+ $extra = "" if !defined($extra);
+
+ if (defined($proxy))
+ {
+ my $p = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21";
+ if ($proxy =~ ":")
+ {
+ # ipv6
+ $p .= "\x21"; # TCP v6
+ $p .= pack "n", 36 + length($extra); # size
+ $p .= inet_pton(AF_INET6, $proxy);
+ $p .= "\0" x 16; # destination address
+ }
+ else
+ {
+ # ipv4
+ $p .= "\x11"; # TCP v4
+ $p .= pack "n", 12 + length($extra); # size
+ $p .= inet_pton(AF_INET, $proxy);
+ $p .= "\0\0\0\0"; # destination address
+ }
+ $p .= pack "n", 1919; # source port
+ $p .= pack "n", 0;
+ $p .= $extra;
+ print $socket $p;
+ }
+ print $socket $startup;
+
+ my $in = read_packet($socket);
+ if (defined($shouldfail))
+ {
+ isnt(substr($in, 0, 1), 'R', $what);
+ }
+ else
+ {
+ is(substr($in, 0, 1), 'R', $what);
+ }
+
+ SKIP:
+ {
+ skip "The rest of this test should fail", 3 if (defined($shouldfail));
+
+ is(substr($in, 8, 1), "\0", $what);
+
+ my ($resip, $resport) = split /\|/,
+ $node->safe_psql('postgres',
+ "SELECT client_addr, client_port FROM pg_stat_activity WHERE pid != pg_backend_pid() AND backend_type='client backend'"
+ );
+ is($resip, $shouldbe, $what);
+ if ($proxy)
+ {
+ is($resport, "1919", $what);
+ }
+ else
+ {
+ ok($resport, $what);
+ }
+ }
+
+ $socket->close();
+
+ return;
+}
+
+sub inet_socket
+{
+ my ($port) = @_;
+ return IO::Socket::INET->new(
+ PeerAddr => "127.0.0.1",
+ PeerPort => $port,
+ Proto => "tcp",
+ Type => SOCK_STREAM);
+}
+
+sub unix_socket
+{
+ my ($port) = @_;
+ return IO::Socket::UNIX->new(
+ Peer => $node->host() . "/.s.PGSQL." . $port,
+ Type => SOCK_STREAM);
+}
+
+
+# Test a regular connection first to make sure connecting etc works fine.
+test_connection(
+ inet_socket($node->port()), undef,
+ "normal ipv4 connection", "127.0.0.1");
+test_connection(unix_socket($node->port()),
+ undef, "normal unix connection", "");
+
+# Make sure we can't make a proxy connection until it's allowed
+test_connection(inet_socket($node->port() + 1),
+ "11.22.33.44", "proxy ipv4", "11.22.33.44", 1);
+
+# Allow proxy connections and test them
+$node->append_conf('postgresql.conf', "proxy_servers = 'unix, 127.0.0.1/32'");
+$node->restart();
+
+test_connection(inet_socket($node->port() + 1),
+ "11.22.33.44", "proxy ipv4", "11.22.33.44");
+test_connection(inet_socket($node->port() + 1),
+ "1:2:3:4:5:6::9", "proxy ipv6", "1:2:3:4:5:6:0:9");
+test_connection(unix_socket($node->port() + 1),
+ "11.22.33.44", "proxy unix", "11.22.33.44");
+
+test_connection(unix_socket($node->port() + 1),
+ "11.22.33.44", "proxy unix with extra", "11.22.33.44", undef, "abcdef"x100);