socket-proxy: implement PROXY protocol v1
as specified by the haproxy documentation: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt only protocol v1 is implemented for now, protocol v2 is binary, and will be implemented in the future. the proxy protocol allows the destination/target to know the address/port of the client connectiing. in nginx it's supported by enabling the `proxy_protocol` parameter to the `listen` directive.
This commit is contained in:
committed by
Yu Watanabe
parent
0fb82954ef
commit
450a5a4740
@@ -83,6 +83,17 @@
|
|||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--proxy-protocol=</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Uses the <ulink url="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol</ulink>
|
||||||
|
to communicate with the server. This allows an appropriately configured server to know the real client IP address.
|
||||||
|
Takes the version of the <ulink url="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol</ulink>
|
||||||
|
used, and only supports <literal>v1</literal> for now.
|
||||||
|
Default is not to use a PROXY protocol.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
<refsect1>
|
<refsect1>
|
||||||
@@ -176,6 +187,47 @@ server {
|
|||||||
<example>
|
<example>
|
||||||
<title>Enabling the proxy</title>
|
<title>Enabling the proxy</title>
|
||||||
<programlisting><![CDATA[# systemctl enable --now proxy-to-nginx.socket
|
<programlisting><![CDATA[# systemctl enable --now proxy-to-nginx.socket
|
||||||
|
$ curl http://localhost:80/]]></programlisting>
|
||||||
|
</example>
|
||||||
|
</refsect2>
|
||||||
|
<refsect2>
|
||||||
|
<title>PROXY protocol example with nginx</title>
|
||||||
|
<para>systemd-socket-proxyd and nginx using the PROXY protocol</para>
|
||||||
|
<example>
|
||||||
|
<title>proxy-to-nginx.socket</title>
|
||||||
|
<programlisting><![CDATA[[Socket]
|
||||||
|
ListenStream=80
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target]]></programlisting>
|
||||||
|
</example>
|
||||||
|
<example>
|
||||||
|
<title>proxy-to-nginx.service</title>
|
||||||
|
<programlisting><![CDATA[[Unit]
|
||||||
|
Requires=nginx.service
|
||||||
|
After=nginx.service
|
||||||
|
Requires=proxy-to-nginx.socket
|
||||||
|
After=proxy-to-nginx.socket
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
ExecStart=/usr/lib/systemd/systemd-socket-proxyd --proxy-protocol=v1 /run/nginx/socket
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateNetwork=yes]]></programlisting>
|
||||||
|
</example>
|
||||||
|
<example>
|
||||||
|
<title>nginx.conf</title>
|
||||||
|
<programlisting>
|
||||||
|
<![CDATA[[…]
|
||||||
|
server {
|
||||||
|
listen unix:/run/nginx/socket proxy_protocol;
|
||||||
|
real_ip_header proxy_protocol;
|
||||||
|
[…]]]>
|
||||||
|
</programlisting>
|
||||||
|
</example>
|
||||||
|
<example>
|
||||||
|
<title>Enabling the proxy</title>
|
||||||
|
<programlisting><![CDATA[# systemctl enable --now proxy-to-nginx.socket
|
||||||
$ curl http://localhost:80/]]></programlisting>
|
$ curl http://localhost:80/]]></programlisting>
|
||||||
</example>
|
</example>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
@@ -190,6 +242,8 @@ $ curl http://localhost:80/]]></programlisting>
|
|||||||
<member><citerefentry project='die-net'><refentrytitle>socat</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
<member><citerefentry project='die-net'><refentrytitle>socat</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||||
<member><citerefentry project='die-net'><refentrytitle>nginx</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
<member><citerefentry project='die-net'><refentrytitle>nginx</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||||
<member><citerefentry project='die-net'><refentrytitle>curl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
<member><citerefentry project='die-net'><refentrytitle>curl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||||
|
<member><ulink url="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol specification</ulink></member>
|
||||||
|
<member><ulink url="https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/">nginx: Accepting the PROXY Protocol</ulink></member>
|
||||||
</simplelist></para>
|
</simplelist></para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
</refentry>
|
</refentry>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
#include "event-util.h"
|
#include "event-util.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
#include "format-table.h"
|
#include "format-table.h"
|
||||||
|
#include "in-addr-util.h"
|
||||||
|
#include "io-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "main-func.h"
|
#include "main-func.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
#include "set.h"
|
#include "set.h"
|
||||||
#include "socket-forward.h"
|
#include "socket-forward.h"
|
||||||
#include "socket-util.h"
|
#include "socket-util.h"
|
||||||
|
#include "string-table.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
#include "time-util.h"
|
#include "time-util.h"
|
||||||
@@ -32,6 +35,21 @@ static unsigned arg_connections_max = 256;
|
|||||||
static const char *arg_remote_host = NULL;
|
static const char *arg_remote_host = NULL;
|
||||||
static usec_t arg_exit_idle_time = USEC_INFINITY;
|
static usec_t arg_exit_idle_time = USEC_INFINITY;
|
||||||
|
|
||||||
|
typedef enum ProxyProtocol {
|
||||||
|
PROXY_NONE,
|
||||||
|
PROXY_V1,
|
||||||
|
_PROXY_PROTOCOL_MAX,
|
||||||
|
_PROXY_PROTOCOL_INVALID = -EINVAL,
|
||||||
|
} ProxyProtocol;
|
||||||
|
|
||||||
|
static const char* const proxy_protocol_table[_PROXY_PROTOCOL_MAX] = {
|
||||||
|
[PROXY_V1] = "v1",
|
||||||
|
};
|
||||||
|
|
||||||
|
static ProxyProtocol arg_proxy_protocol = PROXY_NONE;
|
||||||
|
|
||||||
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(proxy_protocol, ProxyProtocol);
|
||||||
|
|
||||||
typedef struct Context {
|
typedef struct Context {
|
||||||
sd_event *event;
|
sd_event *event;
|
||||||
sd_resolve *resolve;
|
sd_resolve *resolve;
|
||||||
@@ -138,11 +156,96 @@ static int connection_forward_done(SocketForward *sf, int error, void *userdata)
|
|||||||
return 0; /* ignore errors, continue serving */
|
return 0; /* ignore errors, continue serving */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int send_proxy_protocol_v1(Connection *c) {
|
||||||
|
_cleanup_free_ char *header = NULL;
|
||||||
|
int r, header_len;
|
||||||
|
union sockaddr_union local_sa, remote_sa;
|
||||||
|
socklen_t sa_len;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
r = sd_is_socket(c->server_fd, AF_UNSPEC, SOCK_STREAM, /* listening= */ 0);
|
||||||
|
if (r < 0) {
|
||||||
|
log_warning_errno(r, "Failed to issue SO_TYPE, reporting fallback proxy data 'UNKNOWN': %m");
|
||||||
|
goto unknown;
|
||||||
|
}
|
||||||
|
if (r == 0) {
|
||||||
|
log_warning("Only TCP is supported by the PROXY protocol, reporting fallback proxy data 'UNKNOWN'.");
|
||||||
|
goto unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
sa_len = sizeof(local_sa);
|
||||||
|
if (getsockname(c->server_fd, &local_sa.sa, &sa_len) < 0) {
|
||||||
|
log_warning_errno(errno, "Failed to get local address (getsockname), reporting fallback proxy data 'UNKNOWN': %m");
|
||||||
|
goto unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
sa_len = sizeof(remote_sa);
|
||||||
|
if (getpeername(c->server_fd, &remote_sa.sa, &sa_len) < 0) {
|
||||||
|
log_warning_errno(errno, "Failed to get remote address (getpeername), reporting fallback proxy data 'UNKNOWN': %m");
|
||||||
|
goto unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *proto = NULL;
|
||||||
|
switch (remote_sa.sa.sa_family) {
|
||||||
|
|
||||||
|
case AF_INET:
|
||||||
|
proto = "TCP4";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AF_INET6:
|
||||||
|
proto = "TCP6";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
log_warning("Only TCP over IPv4 and IPv6 are supported, reporting fallback proxy data 'UNKNOWN'.");
|
||||||
|
goto unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const union in_addr_union *remote_addr = sockaddr_in_addr(&remote_sa.sa);
|
||||||
|
const union in_addr_union *local_addr = sockaddr_in_addr(&local_sa.sa);
|
||||||
|
|
||||||
|
unsigned remote_port, local_port;
|
||||||
|
assert_se(sockaddr_port(&remote_sa.sa, &remote_port) >= 0);
|
||||||
|
assert_se(sockaddr_port(&local_sa.sa, &local_port) >= 0);
|
||||||
|
|
||||||
|
header_len = asprintf(
|
||||||
|
&header, "PROXY %s %s %s %u %u\r\n",
|
||||||
|
proto,
|
||||||
|
IN_ADDR_TO_STRING(remote_sa.sa.sa_family, remote_addr),
|
||||||
|
IN_ADDR_TO_STRING(local_sa.sa.sa_family, local_addr),
|
||||||
|
remote_port,
|
||||||
|
local_port);
|
||||||
|
if (header_len < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = loop_write_full(c->client_fd, header, header_len, 10 * USEC_PER_SEC);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to write to backend host: %m");
|
||||||
|
|
||||||
|
/* success */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
unknown:
|
||||||
|
/* ignore previous errors - server can decide to deny UNKNOWN connections */
|
||||||
|
r = loop_write_full(c->client_fd, "PROXY UNKNOWN\r\n", SIZE_MAX, 10 * USEC_PER_SEC);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to write to backend host: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int connection_complete(Connection *c) {
|
static int connection_complete(Connection *c) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(c);
|
assert(c);
|
||||||
|
|
||||||
|
if (arg_proxy_protocol == PROXY_V1) {
|
||||||
|
r = send_proxy_protocol_v1(c);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
r = socket_forward_new(
|
r = socket_forward_new(
|
||||||
c->context->event,
|
c->context->event,
|
||||||
TAKE_FD(c->server_fd),
|
TAKE_FD(c->server_fd),
|
||||||
@@ -450,6 +553,13 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", opts.arg);
|
return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", opts.arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
OPTION_LONG("proxy-protocol", "v1",
|
||||||
|
"Enable PROXY protocol: v1"):
|
||||||
|
arg_proxy_protocol = proxy_protocol_from_string(opts.arg);
|
||||||
|
if (arg_proxy_protocol < 0)
|
||||||
|
return log_error_errno(arg_proxy_protocol, "Failed to parse --proxy-protocol= argument: %s", opts.arg);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
char **args = option_parser_get_args(&opts);
|
char **args = option_parser_get_args(&opts);
|
||||||
|
|||||||
Reference in New Issue
Block a user