From: Christoph Liebender Subject: Re: relayd: add support for PROXY protocol in TCP relays To: tech@openbsd.org, reyk@openbsd.org Date: Thu, 18 Dec 2025 14:26:56 +0100 On 11/19/25 15:54, Kirill A. Korinsky wrote: > On Tue, 18 Nov 2025 20:55:56 +0100, > Christoph Liebender wrote: >> >> [1 ] >> On 11/18/25 00:14, Kirill A. Korinsky wrote: >>> On Sun, 16 Nov 2025 19:31:10 +0100, >>> Christoph Liebender wrote: >>> >>> when XX was v1 and base on tcpdump relayd sends a line: >>> >>> PROXY TCP4 127.0.0.1 0.0.0.0 20042 8080 >>> >>> I don't think that 0.0.0.0 is the right IP here. >> >> Thanks. Fixed. >> >>> I haven't read the structure but QUIT brings a bell. I had send a few >>> requests and all of them contains this QUIT. >> >> I'm sorry for ringing your bell, but that is actually part of the >> protocol. Some bytes of the header are QUIT in ASCII. >> >>> >>> Comments in the code: >>> >>>> + >>>> +int >>>> +is_proxy_protocol_allowed(const struct protocol *p, u_int32_t flags) >>>> +{ >>>> + if (!p) >>>> + return 1; >>>> + >>>> + switch (p->type) { >>>> + case RELAY_PROTO_HTTP: >>>> + if (flags & (F_PROXYV1|F_PROXYV2)) >>>> + return 0; >>>> + break; >>>> + case RELAY_PROTO_DNS: >>>> + if (flags & F_PROXYV1) >>>> + return 0; >>>> + break; >>>> + default: >>>> + break; >>>> + } >>>> + >>>> + return 1; >>>> +} >>> >>> It isn't clear for me why you had difference logic for HTTP and DNS. >> >> I made an argument one before my previous email. But if you insist... >> Attached is a version with the fixes and without any config >> sanitization. It also allows v1 over UDP, and v1/v2 over HTTP. So your >> http protocol xff will work. >> >>> >>>> +#define PROXY_V2_FAM_UNSPEC 0x00 >>>> +#define PROXY_V2_FAM_TCP4 0x11 >>>> +#define PROXY_V2_FAM_UDP4 0x12 >>>> +#define PROXY_V2_FAM_TCP6 0x21 >>>> +#define PROXY_V2_FAM_UDP6 0x22 >>> >>> looks like poor ident >>> >> >> Fixed. >> >> Thanks for taking a look. >> > > I had tested it against nginx and my custom implementation of proxy protocol > in Java application, both works with v1 and v2. > > So, it works and reads OK kirill@ > > >> >> [2 relayd7.patch ] >> diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile >> index 2e1ef449b04..5aacf84fc87 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_protocol.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..8fc2016246e 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,8 @@ 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) { >> @@ -1983,6 +1991,7 @@ relayoptsl : LISTEN ON STRING port opttls { >> free($2); >> YYERROR; >> } >> + >> p->flags |= F_USED; >> rlay->rl_conf.proto = p->id; >> rlay->rl_proto = p; >> @@ -2479,6 +2488,7 @@ lookup(char *s) >> { "prefork", PREFORK }, >> { "priority", PRIORITY }, >> { "protocol", PROTO }, >> + { "proxy-protocol", PROXYPROTO }, >> { "query", QUERYSTR }, >> { "quick", QUICK }, >> { "random", RANDOM }, >> @@ -2518,6 +2528,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_protocol.c b/usr.sbin/relayd/proxy_protocol.c >> new file mode 100644 >> index 00000000000..d832d3f6678 >> --- /dev/null >> +++ b/usr.sbin/relayd/proxy_protocol.c >> @@ -0,0 +1,142 @@ >> +/* >> + * 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" >> + >> +#define PROXY_V2_CMD_PROXY 0x01 >> + >> +#define PROXY_V2_FAM_UNSPEC 0x00 >> +#define PROXY_V2_FAM_TCP4 0x11 >> +#define PROXY_V2_FAM_UDP4 0x12 >> +#define PROXY_V2_FAM_TCP6 0x21 >> +#define PROXY_V2_FAM_UDP6 0x22 >> + >> +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_protocol_v1(const struct rsession *con, struct evbuffer *dstout) >> +{ >> + char ibuf[128], obuf[128]; >> + const char *proxyproto; >> + int ret; >> + >> + bzero(&ibuf, sizeof(ibuf)); >> + bzero(&obuf, sizeof(obuf)); >> + print_host(&con->se_in.ss, ibuf, sizeof(ibuf)); >> + print_host(&con->se_sockname, obuf, sizeof(obuf)); >> + >> + if (con->se_relay->rl_conf.flags & F_UDP) >> + proxyproto = "UNKNOWN"; >> + else { >> + 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_protocol_v2(const struct rsession *con, struct evbuffer *dstout) >> +{ >> + union proxy_v2_addr addr; >> + struct proxy_v2_hdr hdr; >> + const struct relay_config *conf = &con->se_relay->rl_conf; >> + const struct sockaddr_storage *srcss = &con->se_in.ss; >> + const struct sockaddr_storage *dstss = &con->se_sockname; >> + int error; >> + in_port_t srcport = con->se_in.port; >> + in_port_t dstport = conf->port; >> + u_int16_t len; >> + >> + bcopy(PROXY_V2_SIG, hdr.sig, sizeof(hdr.sig)); >> + hdr.ver_cmd = 0x20 | PROXY_V2_CMD_PROXY; >> + >> + switch (dstss->ss_family) { >> + case AF_INET: >> + hdr.fam = (conf->flags & F_UDP) ? >> + PROXY_V2_FAM_UDP4 : PROXY_V2_FAM_TCP4; >> + len = sizeof(addr.ipv4_addr); >> + addr.ipv4_addr.src_addr = >> + ((const struct sockaddr_in *)srcss)->sin_addr.s_addr; >> + addr.ipv4_addr.dst_addr = >> + ((const struct sockaddr_in *)dstss)->sin_addr.s_addr; >> + addr.ipv4_addr.src_port = srcport; >> + addr.ipv4_addr.dst_port = dstport; >> + break; >> + case AF_INET6: >> + hdr.fam = (conf->flags & F_UDP) ? >> + PROXY_V2_FAM_UDP6 : PROXY_V2_FAM_TCP6; >> + len = sizeof(addr.ipv6_addr); >> + bcopy(&((const struct sockaddr_in6 *)srcss)->sin6_addr, >> + addr.ipv6_addr.src_addr, sizeof(addr.ipv6_addr.src_addr)); >> + bcopy(&((const struct sockaddr_in6 *)dstss)->sin6_addr, >> + addr.ipv6_addr.dst_addr, sizeof(addr.ipv6_addr.dst_addr)); >> + addr.ipv6_addr.src_port = srcport; >> + addr.ipv6_addr.dst_port = dstport; >> + 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..d705ac1552a 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,19 @@ relay_connected(int fd, short sig, void *arg) >> "failed to allocate output buffer event", 0); >> return; >> } >> + >> + error = 0; >> + >> + if (rlay->rl_conf.flags & F_PROXYV1) >> + error = proxy_protocol_v1(con, bev->output); >> + else if (rlay->rl_conf.flags & F_PROXYV2) >> + error = proxy_protocol_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..4fca5b5329f 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_protocol.c */ >> +int proxy_protocol_v1(const struct rsession *, struct evbuffer *); >> +int proxy_protocol_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); > Ping!