diff --git a/man/systemd-socket-proxyd.xml b/man/systemd-socket-proxyd.xml index dde6d888ad..30f248220e 100644 --- a/man/systemd-socket-proxyd.xml +++ b/man/systemd-socket-proxyd.xml @@ -83,6 +83,17 @@ + + + + Uses the PROXY protocol + to communicate with the server. This allows an appropriately configured server to know the real client IP address. + Takes the version of the PROXY protocol + used, and only supports v1 for now. + Default is not to use a PROXY protocol. + + + @@ -176,6 +187,47 @@ server { Enabling the proxy + + + + PROXY protocol example with nginx + systemd-socket-proxyd and nginx using the PROXY protocol + + proxy-to-nginx.socket + + + + proxy-to-nginx.service + + + + nginx.conf + + + + + + Enabling the proxy + @@ -190,6 +242,8 @@ $ curl http://localhost:80/]]> socat1 nginx1 curl1 + PROXY protocol specification + nginx: Accepting the PROXY Protocol diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 77dc903535..8be25f176e 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -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);