From: Christoph Liebender Subject: Re: relayd: add support for PROXY protocol in TCP relays To: tech@openbsd.org, reyk@openbsd.org Date: Sun, 9 Nov 2025 20:06:28 +0100 On 11/9/25 14:54, Kirill A. Korinsky wrote: > On Sun, 09 Nov 2025 11:30:16 +0100, > Christoph Liebender wrote: >> >> No, there is no apparent reason - I had implemented v1 in op@'s gmid(8) >> (in ports) a while back [1] and still had in mind that v1 is relatively >> straight forward - therefore I initially didn't think about v2 at all. >> >> Also, nginx stream proxy module only speaks version 1. That of course >> doesn't mean relayd needs to be limited to v1... Though v2 probably >> needs more effort and is less trivial to debug. >> >> With v1, one can just nc(1) on a port that relayd forwards to, connect >> to the listen port of relayd and see the proxy line in plaintext. >> >> In my opition, the question about supporting v2 is about: >> >> - does v2 offer any functionality that v1 doesn't while possibly being >> useful for relayd? >> - is there any server implementation that relayd would forward to that >> only supports v2, not v1? >> - is the added code complexity of v2 worth the "performance benefits" >> that are stated in the v2 spec? >> >> If this patch gets ok'd and in the future, v2 is supposed to be >> supported as well, it probably makes sense to alter the configuration >> syntax to something like: >> >> proxy-protocol v1 >> >> to at some point add an option to place a "v2" there. >> > > Well, v2 isn't more complicated: read / write fixed header with a bit which > specific a kind of connection and length the payload. > > What's all. > > If I not mistaken the haproxy docs contains a union with example. > Ok then, this patch implements V2 as well. Feel free to test. diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile index 2e1ef449b04..a7b33cbe4f1 100644 --- a/usr.sbin/relayd/Makefile +++ b/usr.sbin/relayd/Makefile @@ -4,8 +4,9 @@ PROG= relayd SRCS= parse.y SRCS+= agentx_control.c ca.c carp.c check_icmp.c check_script.c \ check_tcp.c check_tls.c config.c control.c hce.c log.c \ - name2id.c pfe.c pfe_filter.c pfe_route.c proc.c relay.c \ - relay_http.c relay_udp.c relayd.c shuffle.c ssl.c util.c + name2id.c pfe.c pfe_filter.c pfe_route.c proc.c proxy.c \ + relay.c relay_http.c relay_udp.c relayd.c shuffle.c \ + ssl.c util.c MAN= relayd.8 relayd.conf.5 LDADD= -lagentx -levent -ltls -lssl -lcrypto -lutil diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y index fcdfb8e92e3..49c12d771ec 100644 --- a/usr.sbin/relayd/parse.y +++ b/usr.sbin/relayd/parse.y @@ -1,6 +1,7 @@ /* $OpenBSD: parse.y,v 1.258 2024/10/28 19:56:18 tb Exp $ */ /* + * Copyright (c) 2025 Christoph Liebender * Copyright (c) 2007 - 2014 Reyk Floeter * Copyright (c) 2008 Gilles Chehade * Copyright (c) 2006 Pierre-Yves Ritschard @@ -179,13 +180,13 @@ typedef struct { %token TIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT URL WITH TTL RTABLE %token MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDHE %token EDH TICKETS CONNECTION CONNECTIONS CONTEXT ERRORS STATE CHANGES CHECKS -%token WEBSOCKETS PFLOG CLIENT +%token WEBSOCKETS PFLOG CLIENT PROXYPROTO V1 V2 %token STRING %token NUMBER %type context hostname interface table value path %type http_type loglevel quick %type dstmode flag forwardmode retry -%type opttls opttlsclient +%type opttls opttlsclient optproxyproto %type redirect_proto relay_proto match pflog %type action ruleaf key_option %type port @@ -1103,6 +1104,11 @@ proto : relay_proto PROTO STRING { } ; +optproxyproto : /* empty */ { $$ = 0; } + | PROXYPROTO V1 { $$ = F_PROXYV1; } + | PROXYPROTO V2 { $$ = F_PROXYV2; } + ; + protopts_n : /* empty */ | '{' '}' | '{' optnl protopts_l '}' @@ -1946,7 +1952,7 @@ relayoptsl : LISTEN ON STRING port opttls { tableport = h->port.val[0]; host_free(&al); } - | forwardmode opttlsclient TO forwardspec dstaf { + | forwardmode opttlsclient TO forwardspec dstaf optproxyproto { rlay->rl_conf.fwdmode = $1; if ($1 == FWD_ROUTE) { yyerror("no route for relays"); @@ -1956,6 +1962,7 @@ relayoptsl : LISTEN ON STRING port opttls { rlay->rl_conf.flags |= F_TLSCLIENT; conf->sc_conf.flags |= F_TLSCLIENT; } + rlay->rl_conf.flags |= $6; } | SESSION TIMEOUT NUMBER { if ((rlay->rl_conf.timeout.tv_sec = $3) < 0) { @@ -2479,6 +2486,7 @@ lookup(char *s) { "prefork", PREFORK }, { "priority", PRIORITY }, { "protocol", PROTO }, + { "proxy-protocol", PROXYPROTO }, { "query", QUERYSTR }, { "quick", QUICK }, { "random", RANDOM }, @@ -2518,6 +2526,8 @@ lookup(char *s) { "transparent", TRANSPARENT }, { "ttl", TTL }, { "url", URL }, + { "v1", V1 }, + { "v2", V2 }, { "value", VALUE }, { "websockets", WEBSOCKETS }, { "with", WITH } diff --git a/usr.sbin/relayd/proxy.c b/usr.sbin/relayd/proxy.c new file mode 100644 index 00000000000..22b8f0ed1b8 --- /dev/null +++ b/usr.sbin/relayd/proxy.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025 Christoph Liebender + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "relayd.h" + +static const u_int8_t PROXY_V2_SIG[12] = { + 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A +}; + +struct proxy_v2_hdr { + u_int8_t sig[12]; + u_int8_t ver_cmd; + u_int8_t fam; + u_int16_t len; +}; + +union proxy_v2_addr { + struct { + u_int32_t src_addr; + u_int32_t dst_addr; + in_port_t src_port; + in_port_t dst_port; + } ipv4_addr; + struct { + u_int8_t src_addr[16]; + u_int8_t dst_addr[16]; + in_port_t src_port; + in_port_t dst_port; + } ipv6_addr; +}; + +int +proxy_v1(const struct rsession *con, struct evbuffer *dstout) +{ + char ibuf[128], obuf[128]; + const char *proxyproto; + int ret; + + memset(&ibuf, 0, sizeof(ibuf)); + memset(&obuf, 0, sizeof(obuf)); + print_host(&con->se_in.ss, ibuf, sizeof(ibuf)); + print_host(&con->se_relay->rl_conf.ss, obuf, sizeof(obuf)); + + switch (con->se_in.ss.ss_family) { + case AF_INET: + proxyproto = "TCP4"; + break; + case AF_INET6: + proxyproto = "TCP6"; + break; + default: + proxyproto = "UNKNOWN"; + break; + } + + ret = evbuffer_add_printf(dstout, + "PROXY %s %s %s %d %d\r\n", proxyproto, ibuf, obuf, + ntohs(con->se_in.port), ntohs(con->se_relay->rl_conf.port)); + + return ret == -1 ? -1 : 0; +} + +int +proxy_v2(const struct rsession *con, struct evbuffer *dstout) +{ + union proxy_v2_addr addr; + struct proxy_v2_hdr hdr; + int error; + u_int16_t len; + + bcopy(PROXY_V2_SIG, hdr.sig, sizeof(hdr.sig)); + hdr.ver_cmd = 0x21; + + switch (con->se_in.ss.ss_family) { + case AF_INET: + hdr.fam = 0x11; + len = sizeof(addr.ipv4_addr); + addr.ipv4_addr.src_addr = + ((const struct sockaddr_in *)&con->se_in.ss)->sin_addr.s_addr; + addr.ipv4_addr.dst_addr = + ((const struct sockaddr_in *)&con->se_relay->rl_conf.ss)->sin_addr.s_addr; + addr.ipv4_addr.src_port = con->se_in.port; + addr.ipv4_addr.dst_port = con->se_relay->rl_conf.port; + break; + case AF_INET6: + hdr.fam = 0x21; + len = sizeof(addr.ipv6_addr); + bcopy(&((const struct sockaddr_in6 *)&con->se_in.ss)->sin6_addr, + addr.ipv6_addr.src_addr, sizeof(addr.ipv6_addr.src_addr)); + bcopy(&((const struct sockaddr_in6 *)&con->se_relay->rl_conf.ss)->sin6_addr, + addr.ipv6_addr.dst_addr, sizeof(addr.ipv6_addr.dst_addr)); + addr.ipv6_addr.src_port = con->se_in.port; + addr.ipv6_addr.dst_port = con->se_relay->rl_conf.port; + break; + default: + hdr.fam = 0x00; + len = 0; + break; + } + + hdr.len = htons(len); + + if ((error = evbuffer_add(dstout, &hdr, sizeof(hdr))) != 0) + return error; + + if (len > 0 && (error = evbuffer_add(dstout, &addr, len)) != 0) + return error; + + return 0; +} + diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c index 6d0970802c5..f166ff02172 100644 --- a/usr.sbin/relayd/relay.c +++ b/usr.sbin/relayd/relay.c @@ -1,6 +1,7 @@ /* $OpenBSD: relay.c,v 1.260 2024/10/28 19:56:18 tb Exp $ */ /* + * Copyright (c) 2025 Christoph Liebender * Copyright (c) 2006 - 2014 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any @@ -767,6 +768,20 @@ relay_connected(int fd, short sig, void *arg) "failed to allocate output buffer event", 0); return; } + + if (rlay->rl_proto->type == RELAY_PROTO_TCP) { + error = 0; + if (rlay->rl_conf.flags & F_PROXYV1) + error = proxy_v1(con, bev->output); + else if (rlay->rl_conf.flags & F_PROXYV2) + error = proxy_v2(con, bev->output); + + if (error) { + relay_abort_http(con, 500, "failed to write PROXY header", 0); + return; + } + } + /* write pending output buffer now */ if (bufferevent_write_buffer(bev, con->se_out.output)) { relay_abort_http(con, 500, strerror(errno), 0); diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 index 29a13efbe8a..6f3a35c0fca 100644 --- a/usr.sbin/relayd/relayd.conf.5 +++ b/usr.sbin/relayd/relayd.conf.5 @@ -1,5 +1,6 @@ .\" $OpenBSD: relayd.conf.5,v 1.213 2025/07/08 14:26:45 schwarze Exp $ .\" +.\" Copyright (c) 2025 Christoph Liebender .\" Copyright (c) 2006 - 2016 Reyk Floeter .\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard .\" @@ -673,6 +674,13 @@ option will be used as a tolerance for failed host connections; the connection will be retried for .Ar number more times. +.It Ic proxy-protocol Pq Ic v1 Ns | Ns Ic v2 +Upon connection to the destination, +.Xr relayd 8 +will prepend a PROXY protocol header of the specified version to the relayed +data, with the IP address and port of the remote host as well as the +.Xr relayd 8 +server. .El .It Xo .Ic forward to diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h index 3b5c3987f93..e8f76a8a471 100644 --- a/usr.sbin/relayd/relayd.h +++ b/usr.sbin/relayd/relayd.h @@ -1,6 +1,7 @@ /* $OpenBSD: relayd.h,v 1.276 2024/10/28 19:56:18 tb Exp $ */ /* + * Copyright (c) 2025 Christoph Liebender * Copyright (c) 2006 - 2016 Reyk Floeter * Copyright (c) 2006, 2007 Pierre-Yves Ritschard * Copyright (c) 2003, 2004 Henning Brauer @@ -402,6 +403,8 @@ union hashkey { #define F_HASHKEY 0x08000000 #define F_AGENTX_TRAPONLY 0x10000000 #define F_PFLOG 0x20000000 +#define F_PROXYV1 0x40000000 +#define F_PROXYV2 0x80000000 #define F_BITS \ "\10\01DISABLE\02BACKUP\03USED\04DOWN\05ADD\06DEL\07CHANGED" \ @@ -1166,7 +1169,7 @@ const char *table_check(enum table_check); const char *relay_state(enum relay_state); #endif const char *print_availability(u_long, u_long); -const char *print_host(struct sockaddr_storage *, char *, size_t); +const char *print_host(const struct sockaddr_storage *, char *, size_t); const char *print_time(struct timeval *, struct timeval *, char *, size_t); const char *printb_flags(const u_int32_t, const char *); void getmonotime(struct timeval *); @@ -1476,4 +1479,8 @@ int config_getrelay(struct relayd *, struct imsg *); int config_getrelaytable(struct relayd *, struct imsg *); int config_getrelayfd(struct relayd *, struct imsg *); +/* proxy.c */ +int proxy_v1(const struct rsession *, struct evbuffer *); +int proxy_v2(const struct rsession *, struct evbuffer *); + #endif /* RELAYD_H */ diff --git a/usr.sbin/relayd/util.c b/usr.sbin/relayd/util.c index 751e82c43c8..c82d254821f 100644 --- a/usr.sbin/relayd/util.c +++ b/usr.sbin/relayd/util.c @@ -214,9 +214,9 @@ print_availability(u_long cnt, u_long up) } const char * -print_host(struct sockaddr_storage *ss, char *buf, size_t len) +print_host(const struct sockaddr_storage *ss, char *buf, size_t len) { - if (getnameinfo((struct sockaddr *)ss, ss->ss_len, + if (getnameinfo((const struct sockaddr *)ss, ss->ss_len, buf, len, NULL, 0, NI_NUMERICHOST) != 0) { buf[0] = '\0'; return (NULL);