On Fri, Sep 10, 2021 at 1:44 AM Jacob Champion <pchamp...@vmware.com> wrote:
>
> On Wed, 2021-09-08 at 18:51 +0000, Jacob Champion wrote:
> > I still owe you that overall review. Hoping to get to it this week.
>
> And here it is. I focused on things other than UnwrapProxyConnection()
> for this round, since I think that piece is looking solid.

Thanks!


> > +       if (port->isProxy)
> > +       {
> > +               ereport(LOG,
> > +                               (errcode_for_socket_access(),
> > +                                errmsg("Ident authentication cannot be 
> > used over PROXY connections")));
>
> What are the rules on COMMERROR vs LOG when dealing with authentication
> code? I always thought COMMERROR was required, but I see now that LOG
> (among others) is suppressed to the client during authentication.

I honestly don't know :) In this case, LOG is what's used for all the
other message in errors in ident_inet(), so I picked it for
consistency.


> >  #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 */
> > @@ -2087,7 +2414,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';
>
> Now that we have port->daddr, could these checks be simplified to just
> IS_AF_UNIX(port->daddr...)? Or is there a corner case I'm missing for
> the port->isProxy case?

Yeah, I think they could.


> > +    * 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.
>
> This comment needs to be adjusted after the move; waiting for the
> startup packet comes later, and it looks like we can now tie up 3x the
> timeout for the proxy case.

Good point.


> > +       /* 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);
>
> I think the timeout here could comfortably be substantially less than
> the overall authentication timeout, since the proxy should send its
> header immediately even if the client takes its time with the startup
> packet. The spec suggests allowing 3 seconds minimum to cover a
> retransmission. Maybe something to tune in the future?

Maybe. I'll leave it with a new comment for now about us diong it, and
that we may want to consider igt in the future.

>
> > +                       /* 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);
>
> Sorry if you already talked about this upthread somewhere, but it looks
> like another downside of treating "proxy mode" as a server-wide on/off
> switch is that it cuts the effective MAXLISTEN in half, from 64 to 32,
> since we're opening two sockets for every address. If I've understood
> that correctly, it might be worth mentioning in the docs.

Correct. I don't see a way to avoid that without complicating things
(as long as we want the ports to be separate), but I also don't see it
as something that's reality to be an issue in reality.

I would agree with documenting it, but I can't actually find us
documenting the MAXLISTEN value anywhere. Do we?


> > -               if (!success && elemlist != NIL)
> > +               if (socket == NULL && elemlist != NIL)
> >                         ereport(FATAL,
> >                                         (errmsg("could not create any 
> > TCP/IP sockets")));
>
> With this change in PostmasterMain, it looks like `success` is no
> longer a useful variable. But I'm not convinced that this is the
> correct logic -- this is just checking to see if the last socket
> creation succeeded, as opposed to seeing if any of them succeeded. Is
> that what you intended?

Eh, no, that's clearly a code-moving-bug.

I think the reasonable thing is to succeed if we create either a
regular socket *or* a proxy one, but FATAL out if you configured
either of them but we failed co create any.

> > +plan tests => 25;
> > +
> > +my $node = get_new_node('node');
>
> The TAP test will need to be rebased over the changes in 201a76183e.

Done, and adjustments above according to your comments, along with a
small docs fix "a proxy connection is done" -> "a proxy connection is
made".


-- 
 Magnus Hagander
 Me: https://www.hagander.net/
 Work: https://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 0a8e35c59f..b1b3613fd1 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 78812b2dbe..8af4d8b69c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22213,7 +22213,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>
@@ -22229,7 +22234,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 d2ce4a8450..dc8a44b0af 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"
@@ -234,6 +236,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,
@@ -2382,6 +2386,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."),
@@ -4355,6 +4369,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
@@ -12559,4 +12584,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 3fe9a53cb3..aec19705ab 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..ad560da6f7
--- /dev/null
+++ b/src/test/protocol/t/001_proxy.pl
@@ -0,0 +1,151 @@
+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 => 25;
+
+my $node = PostgresNode->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 ($PostgresNode::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", $PostgresNode::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);

Reply via email to