On Wed, Mar 3, 2021 at 10:00 AM Magnus Hagander <[email protected]> wrote:
>
> On Wed, Mar 3, 2021 at 1:50 AM Jacob Champion <[email protected]> wrote:
> >
> > On Tue, 2021-03-02 at 18:43 +0100, Magnus Hagander wrote:
> > > PFA a simple patch that implements support for the PROXY protocol.
> >
> > I'm not all the way through the patch yet, but this part jumped out at
> > me:
> >
> > > + if (memcmp(proxyheader.sig,
> > > "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a",
> > > sizeof(proxyheader.sig)) != 0)
> > > + {
> > > + /*
> > > + * Data is there but it wasn't a proxy header. Also fall
> > > through to
> > > + * normal processing
> > > + */
> > > + pq_endmsgread();
> > > + return STATUS_OK;
> >
> > From my reading, the spec explicitly disallows this sort of fallback
> > behavior:
> >
> > > The receiver MUST be configured to only receive the protocol described in
> > > this
> > > specification and MUST not try to guess whether the protocol header is
> > > present
> > > or not. This means that the protocol explicitly prevents port sharing
> > > between
> > > public and private access.
> >
> > You might say, "if we already trust the proxy server, why should we
> > care?" but I think the point is that you want to catch
> > misconfigurations where the middlebox is forwarding bare TCP without
> > adding a PROXY header of its own, which will "work" for innocent
> > clients but in reality is a ticking timebomb. If you've decided to
> > trust an intermediary to use PROXY connections, then you must _only_
> > accept PROXY connections from that intermediary. Does that seem like a
> > reasonable interpretation?
>
> I definitely missed that part of the spec. Ugh.
>
> That said, I'm not sure it's *actually* an issue in the case of
> PostgreSQL. Given that doing what you're suggesting, accidentally
> passing connections without PROXY, will get caught in pg_hba.conf.
>
> That said, I agree with your interpretation, and it's pretty easy to
> change it to that. Basically we just have to do the IP check *before*
> doing the PROXY protocol check. It makes testing a bit more difficult
> though, but maybe worth it?
>
> I've attached a POC that does that. Note that I have *not* updated the docs!
>
> Another option would of course be to listen on a separate port for it,
> which seems to be the "haproxy way". That would be slightly more code
> (we'd still want to keep the code for validating the list of trusted
> proxies I'd say), but maybe worth doing?
In order to figure that out, I hacked up a poc on that. Once again
without updates to the docs, but shows approximately how much code
complexity it adds (not much).
--
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 b420486a0a..d4f6fad5b0 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-servers"/> 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 b5718fc136..fc7de25378 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -682,6 +682,30 @@ include_dir 'conf.d'
</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 host names or cidr specifications
+ of proxy servers to trust. If a connection using the PROXY protocol is made
+ from one of these IP addresses, <productname>PostgreSQL</productname> will
+ read the client IP address from the PROXY header and consider that the
+ address of the client, instead of listing all connections as coming from
+ the proxy server.
+ </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. This parameter can only
+ be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-max-connections" xreflabel="max_connections">
<term><varname>max_connections</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 27a298f110..401f2d2464 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -53,6 +53,7 @@
* pq_getmessage - get a message with length word from connection
* pq_getbyte - get next byte from connection
* pq_peekbyte - peek at next byte from connection
+ * pq_peekbytes - peek at a known number of bytes from connection
* pq_putbytes - send bytes to connection (not flushed until pq_flush)
* pq_flush - flush pending output
* pq_flush_if_writable - flush pending output if writable without blocking
@@ -336,7 +337,7 @@ socket_close(int code, Datum arg)
int
StreamServerPort(int family, const char *hostName, unsigned short portNumber,
const char *unixSocketDir,
- pgsocket ListenSocket[], int MaxListen)
+ pgsocket ListenSocket[], bool ProxyList[], bool isProxy, int MaxListen)
{
pgsocket fd;
int err;
@@ -602,6 +603,7 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
familyDesc, addrDesc, (int) portNumber)));
ListenSocket[listen_index] = fd;
+ ProxyList[listen_index] = isProxy;
added++;
}
@@ -1039,6 +1041,27 @@ pq_peekbyte(void)
return (unsigned char) PqRecvBuffer[PqRecvPointer];
}
+
+/* --------------------------------
+ * pq_peekbytes - peek at a known number of bytes from connection.
+ * Note! Does NOT wait for more data to arrive.
+ *
+ * returns 0 if OK, EOF if trouble
+ * --------------------------------
+ */
+int
+pq_peekbytes(char *s, size_t len)
+{
+ Assert(PqCommReadingMsg);
+
+ if (PqRecvLength - PqRecvPointer < len)
+ return EOF;
+
+ memcpy(s, PqRecvBuffer + PqRecvPointer, len);
+
+ return 0;
+}
+
/* --------------------------------
* pq_getbyte_if_available - get a single byte from connection,
* if available
@@ -1135,7 +1158,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 3f1ce135a8..5b1e4f5592 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -102,6 +102,7 @@
#include "common/string.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
+#include "libpq/ifaddr.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
@@ -195,15 +196,22 @@ BackgroundWorker *MyBgworkerEntry = NULL;
-/* The socket number we are listening for connections on */
+/* The TCP port number we are listening for connections on */
int PostPortNumber;
+/* The TCP port number we are listening for proxy connections on */
+int ProxyPortNumber;
+
/* The directory names for Unix socket(s) */
char *Unix_socket_directories;
/* The TCP listen address(es) */
char *ListenAddresses;
+/* Trusted proxy servers */
+char *TrustedProxyServersString = NULL;
+struct sockaddr_storage *TrustedProxyServers = NULL;
+
/*
* ReservedBackends is the number of backends reserved for superuser use.
* This number is taken out of the pool size given by MaxConnections so
@@ -218,6 +226,7 @@ int ReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
static pgsocket ListenSocket[MAXLISTEN];
+static bool ListenSocketIsProxy[MAXLISTEN];
/*
* These globals control the behavior of the postmaster in case some
@@ -1124,7 +1133,10 @@ PostmasterMain(int argc, char *argv[])
* charged with closing the sockets again at postmaster shutdown.
*/
for (i = 0; i < MAXLISTEN; i++)
+ {
ListenSocket[i] = PGINVALID_SOCKET;
+ ListenSocketIsProxy[i] = false;
+ }
on_proc_exit(CloseServerPorts, 0);
@@ -1156,12 +1168,14 @@ PostmasterMain(int argc, char *argv[])
status = StreamServerPort(AF_UNSPEC, NULL,
(unsigned short) PostPortNumber,
NULL,
- ListenSocket, MAXLISTEN);
+ ListenSocket, ListenSocketIsProxy,
+ false, MAXLISTEN);
else
status = StreamServerPort(AF_UNSPEC, curhost,
(unsigned short) PostPortNumber,
NULL,
- ListenSocket, MAXLISTEN);
+ ListenSocket, ListenSocketIsProxy,
+ false, MAXLISTEN);
if (status == STATUS_OK)
{
@@ -1177,6 +1191,27 @@ 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)
+ status = StreamServerPort(AF_UNSPEC, NULL,
+ (unsigned short) ProxyPortNumber,
+ NULL,
+ ListenSocket, ListenSocketIsProxy,
+ true, MAXLISTEN);
+ else
+ status = StreamServerPort(AF_UNSPEC, curhost,
+ (unsigned short) ProxyPortNumber,
+ NULL,
+ ListenSocket, ListenSocketIsProxy,
+ true, MAXLISTEN);
+ if (status != STATUS_OK)
+ ereport(WARNING,
+ (errmsg("could not create PROXY listen socket for \"%s\"",
+ curhost)));
+ }
}
if (!success && elemlist != NIL)
@@ -1254,7 +1289,8 @@ PostmasterMain(int argc, char *argv[])
status = StreamServerPort(AF_UNIX, NULL,
(unsigned short) PostPortNumber,
socketdir,
- ListenSocket, MAXLISTEN);
+ ListenSocket, ListenSocketIsProxy,
+ false, MAXLISTEN);
if (status == STATUS_OK)
{
@@ -1731,6 +1767,8 @@ ServerLoop(void)
port = ConnCreate(ListenSocket[i]);
if (port)
{
+ port->isProxy = ListenSocketIsProxy[i];
+
BackendStartup(port);
/*
@@ -1911,6 +1949,190 @@ initMasks(fd_set *rmask)
return maxsock + 1;
}
+static int
+UnwrapProxyConnection(Port *port)
+{
+ char proxyver;
+ uint16 proxyaddrlen;
+ SockAddr raddr_save;
+ int i;
+ bool useproxy = false;
+
+ /*
+ * These structs are from the PROXY protocol docs at
+ * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
+ */
+ union
+ {
+ struct
+ { /* for TCP/UDP over IPv4, len = 12 */
+ uint32 src_addr;
+ uint32 dst_addr;
+ uint16 src_port;
+ uint16 dst_port;
+ } ip4;
+ struct
+ { /* for TCP/UDP over IPv6, len = 36 */
+ uint8 src_addr[16];
+ uint8 dst_addr[16];
+ uint16 src_port;
+ uint16 dst_port;
+ } ip6;
+ } proxyaddr;
+ struct
+ {
+ uint8 sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ uint8 ver_cmd; /* protocol version and command */
+ uint8 fam; /* protocol family and address */
+ uint16 len; /* number of following bytes part of the
+ * header */
+ } proxyheader;
+
+
+ if (TrustedProxyServers)
+ {
+ for (i = 0; i < *((int *) TrustedProxyServers); i += 2)
+ {
+ if (port->raddr.addr.ss_family == TrustedProxyServers[i + 1].ss_family &&
+ 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, port->raddr.salen);
+
+ pq_startmsgread();
+
+ /*
+ * PROXY requests always start with: \x0D \x0A \x0D \x0A \x00 \x0D \x0A
+ * \x51 \x55 \x49 \x54 \x0A
+ */
+
+ if (pq_getbytes((char *) &proxyheader, sizeof(proxyheader)) != 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+
+ if (memcmp(proxyheader.sig, "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a", sizeof(proxyheader.sig)) != 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy packet")));
+ return STATUS_ERROR;
+ }
+
+ /* Proxy version is in the high 4 bits of the first byte */
+ proxyver = (proxyheader.ver_cmd & 0xF0) >> 4;
+ if (proxyver != 2)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol version: %x", proxyver)));
+ return STATUS_ERROR;
+ }
+
+ proxyaddrlen = pg_ntoh16(proxyheader.len);
+
+ if (proxyaddrlen > sizeof(proxyaddr))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("oversized proxy packet")));
+ return STATUS_ERROR;
+ }
+ if (pq_getbytes((char *) &proxyaddr, proxyaddrlen) == EOF)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete proxy packet")));
+ return STATUS_ERROR;
+ }
+
+ /* Lower 4 bits hold type of connection */
+ if (proxyheader.fam == 0)
+ {
+ /* LOCAL connection, so we ignore the address included */
+ }
+ else if (proxyheader.fam == 0x11)
+ {
+ /* TCPv4 */
+ port->raddr.addr.ss_family = AF_INET;
+ port->raddr.salen = sizeof(struct sockaddr_in);
+ ((struct sockaddr_in *) &port->raddr.addr)->sin_addr.s_addr = proxyaddr.ip4.src_addr;
+ ((struct sockaddr_in *) &port->raddr.addr)->sin_port = proxyaddr.ip4.src_port;
+ }
+ else if (proxyheader.fam == 0x21)
+ {
+ /* TCPv6 */
+ port->raddr.addr.ss_family = AF_INET6;
+ port->raddr.salen = sizeof(struct sockaddr_in6);
+ memcpy(&((struct sockaddr_in6 *) &port->raddr.addr)->sin6_addr, proxyaddr.ip6.src_addr, 16);
+ ((struct sockaddr_in6 *) &port->raddr.addr)->sin6_port = proxyaddr.ip6.src_port;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid proxy protocol connection type: %x", proxyheader.fam)));
+ return STATUS_ERROR;
+ }
+
+ /* If there is any more header data present, skip past it */
+ if (proxyaddrlen > sizeof(proxyaddr))
+ pq_discardbytes(proxyaddrlen - sizeof(proxyaddr));
+
+
+ pq_endmsgread();
+
+ /*
+ * Log what we've done if connection logging is enabled. We log the proxy
+ * connection here, and let the normal connection logging mechanism log
+ * the unwrapped connection.
+ */
+ if (Log_connections)
+ {
+ char remote_host[NI_MAXHOST];
+ char remote_port[NI_MAXSERV];
+ int ret;
+
+ remote_host[0] = '\0';
+ remote_port[0] = '\0';
+ if ((ret = pg_getnameinfo_all(&raddr_save.addr, raddr_save.salen,
+ remote_host, sizeof(remote_host),
+ remote_port, sizeof(remote_port),
+ (log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0)
+ ereport(WARNING,
+ (errmsg_internal("pg_getnameinfo_all() failed: %s",
+ gai_strerror(ret))));
+
+ ereport(LOG,
+ (errmsg("proxy connection from: host=%s port=%s",
+ remote_host,
+ remote_port)));
+
+ }
+
+ return STATUS_OK;
+}
/*
* Read a client's startup packet and do something according to it.
@@ -4344,6 +4566,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.
*/
@@ -4395,28 +4644,11 @@ BackendInitialize(Port *port)
strspn(remote_host, "0123456789ABCDEFabcdef:") < strlen(remote_host))
port->remote_hostname = strdup(remote_host);
- /*
- * Ready to begin client interaction. We will give up and _exit(1) after
- * a time delay, so that a broken client can't hog a connection
- * indefinitely. PreAuthDelay and any DNS interactions above don't count
- * against the time limit.
- *
- * Note: AuthenticationTimeout is applied here while waiting for the
- * startup packet, and then again in InitPostgres for the duration of any
- * authentication operations. So a hostile client could tie up the
- * process for nearly twice AuthenticationTimeout before we kick him off.
- *
- * Note: because PostgresMain will call InitializeTimeouts again, the
- * registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay
- * since we never use it again after this function.
- */
- RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
- enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
-
/*
* Receive the startup packet (which might turn out to be a cancel request
* packet).
*/
+ enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
status = ProcessStartupPacket(port, false, false);
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d626731723..9be50b1532 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -46,10 +46,12 @@
#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/variable.h"
+#include "common/ip.h"
#include "common/string.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "libpq/auth.h"
+#include "libpq/ifaddr.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
@@ -227,6 +229,8 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou
static void assign_recovery_target_lsn(const char *newval, void *extra);
static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
+static bool check_proxy_servers(char **newval, void **extra, GucSource source);
+static void assign_proxy_servers(const char *newval, void *extra);
/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2290,6 +2294,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."),
@@ -4241,6 +4255,17 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"proxy_servers", PGC_POSTMASTER, 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
@@ -12228,4 +12253,108 @@ 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;
+
+ netmasktok = strchr(tok, '/');
+ if (netmasktok)
+ {
+ *netmasktok = '\0';
+ netmasktok++;
+ }
+
+ memset((char *) &hints, 0, sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+
+ ret = pg_getaddrinfo_all(tok, NULL, &hints, &gai_result);
+ if (ret != 0 || gai_result == NULL)
+ {
+ GUC_check_errdetail("Invalid IP addrress %s", tok);
+ pfree(rawstring);
+ list_free(elemlist);
+ free(myextra);
+ return false;
+ }
+
+ memcpy((char *) &myextra[foreach_current_index(l) * 2 + 1], gai_result->ai_addr, gai_result->ai_addrlen);
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+
+ /* A NULL netmasktok means the fully set hostmask */
+ if (pg_sockaddr_cidr_mask(&myextra[foreach_current_index(l) * 2 + 2], netmasktok, myextra[foreach_current_index(l) * 2 + 1].ss_family) != 0)
+ {
+ if (netmasktok)
+ GUC_check_errdetail("Invalid netmask %s", netmasktok);
+ else
+ GUC_check_errdetail("Could not create netmask");
+ pfree(rawstring);
+ list_free(elemlist);
+ free(myextra);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+assign_proxy_servers(const char *newval, void *extra)
+{
+ TrustedProxyServers = (struct sockaddr_storage *) extra;
+}
+
#include "guc-file.c"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee06528bb0..aa7ac35f67 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -61,6 +61,8 @@
# defaults to 'localhost'; use '*' for all
# (change requires restart)
#port = 5432 # (change requires restart)
+#proxy_servers = '' # what IP/netmasks of proxy servers to trust
+ # (change requires restart)
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..57edda122a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -121,6 +121,7 @@ typedef struct Port
{
pgsocket sock; /* File descriptor */
bool noblock; /* is the socket in non-blocking mode? */
+ bool isProxy; /* is the connection using PROXY protocol */
ProtocolVersion proto; /* FE/BE protocol version */
SockAddr laddr; /* local addr (postmaster) */
SockAddr raddr; /* remote addr (client) */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index e4e5c21565..549e2d86a7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -60,7 +60,7 @@ extern WaitEventSet *FeBeWaitSet;
extern int StreamServerPort(int family, const char *hostName,
unsigned short portNumber, const char *unixSocketDir,
- pgsocket ListenSocket[], int MaxListen);
+ pgsocket ListenSocket[], bool ProxyList[], bool isProxy, int MaxListen);
extern int StreamConnection(pgsocket server_fd, Port *port);
extern void StreamClose(pgsocket sock);
extern void TouchSocketFiles(void);
@@ -74,6 +74,8 @@ 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_peekbytes(char *s, size_t len);
+extern int pq_discardbytes(size_t len);
extern int pq_getbyte_if_available(unsigned char *c);
extern int pq_putbytes(const char *s, size_t len);
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index cfa59c4dc0..9ed219dfda 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -17,10 +17,13 @@
extern bool EnableSSL;
extern int ReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
+extern PGDLLIMPORT int ProxyPortNumber;
extern int Unix_socket_permissions;
extern char *Unix_socket_group;
extern char *Unix_socket_directories;
extern char *ListenAddresses;
+extern char *TrustedProxyServersString;
+extern struct sockaddr_storage *TrustedProxyServers;
extern bool ClientAuthInProgress;
extern int PreAuthDelay;
extern int AuthenticationTimeout;