On Wed, Nov 3, 2021 at 2:36 PM Daniel Gustafsson <[email protected]> wrote:
> > On 28 Sep 2021, at 15:23, Magnus Hagander <[email protected]> wrote:
> > On Fri, Sep 10, 2021 at 1:44 AM Jacob Champion <[email protected]>
> wrote:
>
> >> The TAP test will need to be rebased over the changes in 201a76183e.
> >
> > Done
>
> And now the TAP test will need to be rebased over the changes in
> b3b4d8e68ae83f432f43f035c7eb481ef93e1583.
>
Thanks for the pointer, PFA a rebase.
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/>
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 02f0489112..a3ff09b3ac 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -353,6 +353,15 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl
the client's host name instead of the IP address in the log.
</para>
+ <para>
+ If <xref linkend="guc-proxy-port"/> 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.
+ </para>
+
<para>
These fields do not apply to <literal>local</literal> records.
</para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index de77f14573..5211c1f7b1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -682,6 +682,56 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-proxy-port" xreflabel="proxy_port">
+ <term><varname>proxy_port</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>proxy_port</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The TCP port the server listens on for PROXY connections, disabled by
+ default. If set to a number, <productname>PostgreSQL</productname>
+ 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.
+ </para>
+ <para>
+ If a proxy connection is made over this port, and the proxy is listed
+ in <xref linkend="guc-proxy-servers" />, the actual client address
+ will be considered as the address of the client, instead of listing
+ all connections as coming from the proxy server.
+ </para>
+ <para>
+ The <ulink url="http://www.haproxy.org/download/1.9/doc/proxy-protocol.txt">PROXY
+ protocol</ulink> is maintained by <productname>HAProxy</productname>,
+ and supported in many proxies and load
+ balancers. <productname>PostgreSQL</productname> supports version 2
+ of the protocol.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-proxy-servers" xreflabel="proxy_servers">
+ <term><varname>proxy_servers</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>proxy_servers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ A comma separated list of one or more ip addresses, cidr specifications or the
+ literal <literal>unix</literal>, indicating which proxy servers to trust when
+ connecting on the port specified in <xref linkend="guc-proxy-port" />.
+ </para>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-max-connections" xreflabel="max_connections">
<term><varname>max_connections</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4b49dff2ff..74f38d4891 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22238,7 +22238,12 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
connection,
or <literal>NULL</literal> if the current connection is via a
Unix-domain socket.
- </para></entry>
+ </para>
+ <para>
+ If the connection is a PROXY connection, this function returns the
+ IP address used to connect to the proxy server.
+ </para>
+ </entry>
</row>
<row>
@@ -22254,7 +22259,13 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
connection,
or <literal>NULL</literal> if the current connection is via a
Unix-domain socket.
- </para></entry>
+ </para>
+ <para>
+ If the connection is a PROXY connection, this function returns the
+ port used to connect to the proxy server.
+ </para>
+
+ </entry>
</row>
<row>
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..f8c32ad492 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1696,6 +1696,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 89a5f901aa..a8d6c5fa4c 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -311,13 +311,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;
@@ -362,10 +362,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
@@ -388,7 +388,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)
@@ -405,7 +405,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)
@@ -584,16 +584,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];
}
@@ -747,6 +747,9 @@ StreamConnection(pgsocket server_fd, Port *port)
return STATUS_ERROR;
}
+ /* copy over to daddr to make sure it's set for the non-proxy case */
+ memcpy(&port->daddr, &port->laddr, sizeof(port->laddr));
+
/* select NODELAY and KEEPALIVE options if it's a TCP connection */
if (!IS_AF_UNIX(port->laddr.addr.ss_family))
{
@@ -1118,7 +1121,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 e2a76ba055..797a317a04 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -101,6 +101,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"
@@ -196,15 +197,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
@@ -218,7 +226,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
@@ -586,6 +594,7 @@ PostmasterMain(int argc, char *argv[])
bool listen_addr_saved = false;
int i;
char *output_config_variable = NULL;
+ PQlistenSocket *socket = NULL;
InitProcessGlobals();
@@ -1185,7 +1194,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);
@@ -1214,17 +1226,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 */
@@ -1238,6 +1250,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)
+ {
+ success++;
+ socket->isProxy = true;
+ }
+ else
+ ereport(WARNING,
+ (errmsg("could not create PROXY listen socket for \"%s\"",
+ curhost)));
+ }
}
if (!success && elemlist != NIL)
@@ -1250,7 +1286,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;
@@ -1312,12 +1348,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 */
@@ -1328,9 +1364,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")));
@@ -1342,7 +1392,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")));
@@ -1497,10 +1547,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;
}
}
@@ -1789,15 +1839,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);
/*
@@ -1965,7 +2017,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;
@@ -1978,6 +2030,284 @@ initMasks(fd_set *rmask)
return maxsock + 1;
}
+static int
+UnwrapProxyConnection(Port *port)
+{
+ char proxyver;
+ uint16 proxyaddrlen;
+ SockAddr raddr_save;
+ SockAddr laddr_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;
+
+ /*
+ * Assert the size of the structs that are part of the protocol,
+ * to defend against strange compilers.
+ */
+ StaticAssertStmt(sizeof(proxyheader) == 16, "proxy header struct has invalid size");
+ StaticAssertStmt(sizeof(proxyaddr.ip4) == 12, "proxy address ipv4 struct has invalid size");
+ StaticAssertStmt(sizeof(proxyaddr.ip6) == 36, "proxy address ipv6 struct has invalid size");
+
+
+ /* 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));
+ memcpy(&laddr_save, &port->laddr, 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;
+ }
+
+ /*
+ * Proxy command is in the low 4 bits of the first byte.
+ * 0x00 = local, 0x01 = proxy, all others should be rejected
+ */
+ if ((proxyheader.ver_cmd & 0x0F) == 0x00)
+ {
+ if (proxyheader.fam != 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol family %x for local connection", proxyheader.fam)));
+ return STATUS_ERROR;
+ }
+ }
+ else if ((proxyheader.ver_cmd & 0x0F) == 0x01)
+ {
+ if (proxyheader.fam == 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol family 0 for non-local connection")));
+ return STATUS_ERROR;
+ }
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol command: %x", (proxyheader.ver_cmd & 0x0f))));
+ 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;
+ }
+
+ /* Connection family */
+ if (proxyheader.fam == 0)
+ {
+ /*
+ * UNSPEC connection over LOCAL (verified above).
+ * in this case we just 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;
+
+ port->daddr.addr.ss_family = AF_INET;
+ port->daddr.salen = sizeof(struct sockaddr_in);
+ ((struct sockaddr_in *) &port->daddr.addr)->sin_addr.s_addr = proxyaddr.ip4.dst_addr;
+ ((struct sockaddr_in *) &port->daddr.addr)->sin_port = proxyaddr.ip4.dst_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;
+
+
+ port->daddr.addr.ss_family = AF_INET6;
+ port->daddr.salen = sizeof(struct sockaddr_in6);
+ memcpy(&((struct sockaddr_in6 *) &port->daddr.addr)->sin6_addr, proxyaddr.ip6.dst_addr, 16);
+ ((struct sockaddr_in6 *) &port->daddr.addr)->sin6_port = proxyaddr.ip6.dst_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];
+ char proxy_host[NI_MAXHOST];
+ char proxy_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))));
+
+ proxy_host[0] = '\0';
+ proxy_port[0] = '\0';
+ if ((ret = pg_getnameinfo_all(&laddr_save.addr, laddr_save.salen,
+ proxy_host, sizeof(proxy_host),
+ proxy_port, sizeof(proxy_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 (proxy host=%s port=%s)",
+ remote_host,
+ remote_port,
+ proxy_host,
+ proxy_port)));
+
+ }
+
+ return STATUS_OK;
+}
/*
* Read a client's startup packet and do something according to it.
@@ -2086,7 +2416,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->daddr.addr.ss_family))
SSLok = 'N';
else
SSLok = 'S'; /* Support for SSL */
@@ -2123,7 +2453,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->daddr.addr.ss_family))
GSSok = 'G';
#endif
@@ -2635,10 +2965,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;
}
}
@@ -4422,6 +4752,31 @@ 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.
+ *
+ * If this is a proxy connection, we apply the timeout once while waiting
+ * for the proxy header. It is then reapplied further down when we process
+ * the startup packet, which means it can apply multiple times.
+ *
+ * For the time being we re-use AuthenticationTimeout for this, but it may
+ * be considered for a separate tunable in the future.
+ */
+ 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.
*/
@@ -4474,27 +4829,20 @@ BackendInitialize(Port *port)
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.
+ * Receive the startup packet (which might turn out to be a cancel request
+ * packet).
*
* 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.
+ * process for nearly twice (or three times in the case of a proxy connection)
+ * 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).
- */
status = ProcessStartupPacket(port, false, false);
/*
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 0ab54316f8..9198a29f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1802,6 +1802,8 @@ inet_client_port(PG_FUNCTION_ARGS)
/*
* IP address that the server accepted the connection on (NULL if Unix socket)
+ * If the connection is a PROXY connection, then this returns the IP address/port of
+ * the proxy server, and not the local connection!
*/
Datum
inet_server_addr(PG_FUNCTION_ARGS)
@@ -1813,7 +1815,7 @@ inet_server_addr(PG_FUNCTION_ARGS)
if (port == NULL)
PG_RETURN_NULL();
- switch (port->laddr.addr.ss_family)
+ switch (port->daddr.addr.ss_family)
{
case AF_INET:
#ifdef HAVE_IPV6
@@ -1826,14 +1828,14 @@ inet_server_addr(PG_FUNCTION_ARGS)
local_host[0] = '\0';
- ret = pg_getnameinfo_all(&port->laddr.addr, port->laddr.salen,
+ ret = pg_getnameinfo_all(&port->daddr.addr, port->daddr.salen,
local_host, sizeof(local_host),
NULL, 0,
NI_NUMERICHOST | NI_NUMERICSERV);
if (ret != 0)
PG_RETURN_NULL();
- clean_ipv6_addr(port->laddr.addr.ss_family, local_host);
+ clean_ipv6_addr(port->daddr.addr.ss_family, local_host);
PG_RETURN_INET_P(network_in(local_host, false));
}
@@ -1841,6 +1843,8 @@ inet_server_addr(PG_FUNCTION_ARGS)
/*
* port that the server accepted the connection on (NULL if Unix socket)
+ * If the connection is a PROXY connection, then this returns the IP address/port of
+ * the proxy server, and not the local connection!
*/
Datum
inet_server_port(PG_FUNCTION_ARGS)
@@ -1852,7 +1856,7 @@ inet_server_port(PG_FUNCTION_ARGS)
if (port == NULL)
PG_RETURN_NULL();
- switch (port->laddr.addr.ss_family)
+ switch (port->daddr.addr.ss_family)
{
case AF_INET:
#ifdef HAVE_IPV6
@@ -1865,7 +1869,7 @@ inet_server_port(PG_FUNCTION_ARGS)
local_port[0] = '\0';
- ret = pg_getnameinfo_all(&port->laddr.addr, port->laddr.salen,
+ ret = pg_getnameinfo_all(&port->daddr.addr, port->daddr.salen,
NULL, 0,
local_port, sizeof(local_port),
NI_NUMERICHOST | NI_NUMERICSERV);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..6aa0e7fca6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -50,10 +50,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"
@@ -235,6 +237,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,
@@ -2383,6 +2387,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."),
@@ -4368,6 +4382,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
@@ -12572,4 +12597,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 address %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 1cbc9feeb6..3e67fa6b3c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -62,6 +62,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 02015efe13..e91a4d7607 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -126,9 +126,11 @@ 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) */
+ SockAddr daddr; /* destination addr (postmaster, or proxy server if proxy protocol used) */
char *remote_host; /* name (or ip addr) of remote host */
char *remote_hostname; /* name (not ip addr) of remote host, if
* available */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..cdaae030e1 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -42,6 +42,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())
@@ -63,9 +69,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);
@@ -78,6 +84,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);
extern bool pq_check_connection(void);
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 0efdd7c232..2a029ef786 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 46275915ff..4ad030034c 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,8 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery subscription \
+ 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..64619058c8
--- /dev/null
+++ b/src/test/protocol/t/001_proxy.pl
@@ -0,0 +1,151 @@
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Socket qw(AF_INET AF_INET6 inet_pton);
+use IO::Socket;
+
+plan tests => 25;
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->append_conf(
+ 'postgresql.conf', qq{
+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;
+
+$node->safe_psql('postgres', 'CREATE USER proxytest;');
+
+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 => "proxytest", 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 make_socket
+{
+ my ($port) = @_;
+ if ($PostgreSQL::Test::Cluster::use_tcp) {
+ return IO::Socket::INET->new(
+ PeerAddr => "127.0.0.1",
+ PeerPort => $port,
+ Proto => "tcp",
+ Type => SOCK_STREAM);
+ }
+ else {
+ 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(make_socket($node->port()),
+ undef, "normal connection", $PostgreSQL::Test::Cluster::use_tcp ? "127.0.0.1": "");
+
+# Make sure we can't make a proxy connection until it's allowed
+test_connection(make_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(make_socket($node->port() + 1),
+ "11.22.33.44", "proxy ipv4", "11.22.33.44");
+test_connection(make_socket($node->port() + 1),
+ "1:2:3:4:5:6::9", "proxy ipv6", "1:2:3:4:5:6:0:9");
+
+test_connection(make_socket($node->port() + 1),
+ "11.22.33.44", "proxy with extra", "11.22.33.44", undef, "abcdef"x100);