On Fri, Jul 9, 2021 at 1:42 AM Jacob Champion <pchamp...@vmware.com> wrote:
>
> Hi Magnus,
>
> I'm only just starting to page this back into my head, so this is by no
> means a full review of the v7 changes -- just stuff I've noticed over
> the last day or so of poking around.
>
> On Tue, 2021-06-29 at 11:48 +0200, Magnus Hagander wrote:
> > On Thu, Mar 11, 2021 at 12:05 AM Jacob Champion <pchamp...@vmware.com> 
> > wrote:
> > > On Tue, 2021-03-09 at 11:25 +0100, Magnus Hagander wrote:
> > > > - \x0 : LOCAL : [...] The receiver must accept this connection as
> > > > valid and must use the real connection endpoints and discard the
> > > > protocol block including the family which is ignored.
> > >
> > > So we should ignore the entire "protocol block" (by which I believe
> > > they mean the protocol-and-address-family byte) in the case of LOCAL,
> > > and just accept it with the original address info intact. That seems to
> > > match the sample code in the back of the spec. The current behavior in
> > > the patch will apply the PROXY behavior incorrectly if the sender sends
> > > a LOCAL header with something other than UNSPEC -- which is strange
> > > behavior but not explicitly prohibited as far as I can see.
> >
> > Yeah, I think we do the right thing in the "right usecase".
>
> The current implementation is, I think, stricter than the spec asks
> for. We're supposed to ignore the family for LOCAL cases, and it's not
> clear to me whether we're supposed to also ignore the entire "fam"
> family-and-protocol byte (the phrase "protocol block" is not actually
> defined in the spec).
>
> It's probably not a big deal in practice, but it could mess with
> interoperability for lazier proxy implementations. I think I'll ask the
> HAProxy folks for some clarification tomorrow.

Thanks!

Yeah, I have no problem being stricter than necessary, unless that
actually causes any interop problems. It's a lot worse to not be
strict enough..


> > +      <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 host names, 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" 
> > />.
>
> The documentation mentions that host names are valid in proxy_servers,
> but check_proxy_servers() uses the AI_NUMERICHOST hint with
> getaddrinfo(), so host names get rejected.

Ah, good point. Should say "ip addresses".

>
> > +           GUC_check_errdetail("Invalid IP addrress %s", tok);
>
> s/addrress/address/

Oops.


> I've been thinking more about your earlier comment:
>
> > An interesting thing is what to do about
> > inet_server_addr/inet_server_port. That sort of loops back up to the
> > original question of where/how to expose the information about the
> > proxy in general (since right now it just logs). Right now you can
> > actually use inet_server_port() to see if the connection was proxied
> > (as long as it was over tcp).
>
> IMO these should return the "virtual" dst_addr/port, instead of
> exposing the physical connection information to the client. That way,
> if you intend for your proxy to be transparent, you're not advertising
> your network internals to connected clients. It also means that clients
> can reasonably expect to be able to reconnect to the addr:port that we
> give them, and prevents confusion if the proxy is using an address
> family that the client doesn't even support (e.g. the client is IPv4-
> only but the proxy connects via IPv6).

That reasoning I think makes a lot of sense, especially with the
comment about being able to connect back to it.

The question at that point extends to, would we also add extra
functions to get the data on the proxy connection itself? Maybe add a
inet_proxy_addr()/inet_proxy_port()? Better names?

PFA a patch that fixes the above errors, and changes
inet_server_addr()/inet_server_port(). Does not yet add anything to
receive the actual local port in this case.

--
 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 381d8636ab..778a20a179 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 done 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 6388385edc..3884412a8b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21872,7 +21872,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>
@@ -21888,7 +21893,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 8cc23ef7fb..65f8e69131 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 5a050898fe..5991fa9f2b 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"
@@ -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();
 
@@ -1135,7 +1144,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);
 
@@ -1164,17 +1176,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 */
@@ -1188,9 +1200,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")));
 
@@ -1200,7 +1233,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;
 
@@ -1262,12 +1295,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 */
@@ -1278,9 +1311,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")));
 
@@ -1292,7 +1339,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")));
 
@@ -1441,10 +1488,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;
 		}
 	}
 
@@ -1733,15 +1780,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);
 
 						/*
@@ -1909,7 +1958,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;
@@ -1922,6 +1971,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.
@@ -2030,7 +2357,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 */
@@ -2067,7 +2394,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
 
@@ -2579,10 +2906,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;
 		}
 	}
 
@@ -4363,6 +4690,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.
 	 */
@@ -4414,28 +4768,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/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 480e8cd199..e2e5af2fd8 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,
@@ -2358,6 +2362,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."),
@@ -4331,6 +4345,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
@@ -12536,4 +12561,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 b696abfe54..2e224fef36 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 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..edc032d49c
--- /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 = get_new_node('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