]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
socket-proxy: implement PROXY protocol v1
authorДамјан Георгиевски <gdamjan@gmail.com>
Mon, 18 May 2026 21:07:37 +0000 (23:07 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 19 May 2026 19:29:59 +0000 (04:29 +0900)
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.

man/systemd-socket-proxyd.xml
src/socket-proxy/socket-proxyd.c

index dde6d888ada582f4ac8ce9114838dd4982121c0f..30f248220ef18d56f341587d9e3726bea2ef7d84 100644 (file)
 
         <xi:include href="version-info.xml" xpointer="v246"/></listitem>
       </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>
   </refsect1>
   <refsect1>
@@ -176,6 +187,47 @@ server {
       <example>
         <title>Enabling the proxy</title>
         <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>
       </example>
     </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>nginx</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>
   </refsect1>
 </refentry>
index 77dc903535633cf9352f76e3f417975b184c67ec..8be25f176e1664a438d23d60aad9f55d98cd773e 100644 (file)
@@ -15,6 +15,8 @@
 #include "event-util.h"
 #include "fd-util.h"
 #include "format-table.h"
+#include "in-addr-util.h"
+#include "io-util.h"
 #include "log.h"
 #include "main-func.h"
 #include "options.h"
@@ -24,6 +26,7 @@
 #include "set.h"
 #include "socket-forward.h"
 #include "socket-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
@@ -32,6 +35,21 @@ static unsigned arg_connections_max = 256;
 static const char *arg_remote_host = NULL;
 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 {
         sd_event *event;
         sd_resolve *resolve;
@@ -138,11 +156,96 @@ static int connection_forward_done(SocketForward *sf, int error, void *userdata)
         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) {
         int r;
 
         assert(c);
 
+        if (arg_proxy_protocol == PROXY_V1) {
+                r = send_proxy_protocol_v1(c);
+                if (r < 0)
+                        return r;
+        }
+
         r = socket_forward_new(
                         c->context->event,
                         TAKE_FD(c->server_fd),
@@ -450,6 +553,13 @@ static int parse_argv(int argc, char *argv[]) {
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", opts.arg);
                         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);