From dd7da8fb7ca51c122b3016329436c75c8a1d3726 Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Thu, 16 Apr 2026 12:07:14 -0700 Subject: [PATCH] Stream: proxy_protocol_crc32c directive for PROXY protocol v2. Add a proxy_protocol_crc32c on|off directive to the stream proxy module. When enabled, a CRC32c checksum TLV (type 0x03, length 4) is appended to the PROXY protocol v2 header before sending it upstream. The checksum is computed over the entire assembled header with the four-byte CRC32c value field zeroed, then written in network byte order, as required by the PROXY protocol spec. Core: add ngx_proxy_protocol_v2_write_crc32c(c, buf, p, last) which appends the zeroed CRC TLV to the buffer at p, updates header->len, computes the CRC32c from buf through the end of the TLV, and fills in the result. --- src/core/ngx_proxy_protocol.c | 48 ++++++++++++++++++++++++++++ src/core/ngx_proxy_protocol.h | 2 ++ src/stream/ngx_stream_proxy_module.c | 40 +++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/src/core/ngx_proxy_protocol.c b/src/core/ngx_proxy_protocol.c index 98b5d87a6..f79e39dab 100644 --- a/src/core/ngx_proxy_protocol.c +++ b/src/core/ngx_proxy_protocol.c @@ -502,6 +502,54 @@ ngx_proxy_protocol_v2_write_tlvs(ngx_connection_t *c, u_char *buf, } +u_char * +ngx_proxy_protocol_v2_write_crc32c(ngx_connection_t *c, u_char *buf, + u_char *p, u_char *last) +{ + uint16_t len; + uint32_t crc; + ngx_proxy_protocol_header_t *header; + ngx_proxy_protocol_tlv_t *wire; + + /* type(1) + len(2) + crc32c_value(4) = 7 bytes */ + if (p + 7 > last) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "too small buffer for PROXY protocol v2 CRC32c TLV"); + return NULL; + } + + /* Append CRC32c TLV with zeroed value field */ + wire = (ngx_proxy_protocol_tlv_t *) p; + wire->type = 0x03; + wire->len[0] = 0; + wire->len[1] = 4; + p += sizeof(ngx_proxy_protocol_tlv_t); + + p[0] = 0; + p[1] = 0; + p[2] = 0; + p[3] = 0; + p += 4; + + /* Update the PPv2 length field to include this TLV */ + header = (ngx_proxy_protocol_header_t *) buf; + len = ngx_proxy_protocol_parse_uint16(header->len) + 7; + header->len[0] = (u_char) (len >> 8); + header->len[1] = (u_char) len; + + /* Compute CRC32c over the entire assembled header (zeroed value included) */ + crc = ngx_crc32c(buf, p - buf); + + /* Write the checksum in network byte order */ + p[-4] = (u_char) (crc >> 24); + p[-3] = (u_char) (crc >> 16); + p[-2] = (u_char) (crc >> 8); + p[-1] = (u_char) crc; + + return p; +} + + static u_char * ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last) { diff --git a/src/core/ngx_proxy_protocol.h b/src/core/ngx_proxy_protocol.h index f3c675b21..71b3f20aa 100644 --- a/src/core/ngx_proxy_protocol.h +++ b/src/core/ngx_proxy_protocol.h @@ -41,6 +41,8 @@ u_char *ngx_proxy_protocol_v2_write(ngx_connection_t *c, u_char *buf, u_char *last); u_char *ngx_proxy_protocol_v2_write_tlvs(ngx_connection_t *c, u_char *buf, u_char *last, ngx_array_t *tlvs); +u_char *ngx_proxy_protocol_v2_write_crc32c(ngx_connection_t *c, u_char *buf, + u_char *p, u_char *last); ngx_int_t ngx_proxy_protocol_get_tlv(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value); diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 5b3ebdd1a..8f129f2aa 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -39,6 +39,7 @@ typedef struct { ngx_flag_t proxy_protocol; ngx_uint_t proxy_protocol_version; ngx_array_t *proxy_protocol_tlvs; + ngx_flag_t proxy_protocol_crc32c; ngx_flag_t half_close; ngx_stream_upstream_local_t *local; ngx_flag_t socket_keepalive; @@ -288,6 +289,13 @@ static ngx_command_t ngx_stream_proxy_commands[] = { 0, NULL }, + { ngx_string("proxy_protocol_crc32c"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, proxy_protocol_crc32c), + NULL }, + { ngx_string("proxy_half_close"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -1026,6 +1034,10 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) buf_size += tlv_size; } + if (pscf->proxy_protocol_version == 2 && pscf->proxy_protocol_crc32c) { + buf_size += 7; /* CRC32c TLV: type(1) + len(2) + value(4) */ + } + p = ngx_pnalloc(c->pool, buf_size); if (p == NULL) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); @@ -1046,6 +1058,15 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) return; } + if (pscf->proxy_protocol_version == 2 && pscf->proxy_protocol_crc32c) { + p = ngx_proxy_protocol_v2_write_crc32c(c, cl->buf->pos, p, + cl->buf->pos + buf_size); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + cl->buf->last = p; cl->buf->temporary = 1; cl->buf->flush = 0; @@ -1110,6 +1131,13 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s) } buf_size += tlv_size; + } + + if (pscf->proxy_protocol_version == 2 && pscf->proxy_protocol_crc32c) { + buf_size += 7; /* CRC32c TLV: type(1) + len(2) + value(4) */ + } + + if (buf_size > NGX_PROXY_PROTOCOL_V2_MAX_HEADER) { buf = ngx_pnalloc(c->pool, buf_size); if (buf == NULL) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); @@ -1129,6 +1157,14 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s) return NGX_ERROR; } + if (pscf->proxy_protocol_version == 2 && pscf->proxy_protocol_crc32c) { + p = ngx_proxy_protocol_v2_write_crc32c(c, buf, p, buf + buf_size); + if (p == NULL) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + } + u = s->upstream; pc = u->peer.connection; @@ -2489,6 +2525,7 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) conf->proxy_protocol = NGX_CONF_UNSET; conf->proxy_protocol_version = NGX_CONF_UNSET_UINT; conf->proxy_protocol_tlvs = NGX_CONF_UNSET_PTR; + conf->proxy_protocol_crc32c = NGX_CONF_UNSET; conf->local = NGX_CONF_UNSET_PTR; conf->socket_keepalive = NGX_CONF_UNSET; conf->half_close = NGX_CONF_UNSET; @@ -2553,6 +2590,9 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->proxy_protocol_tlvs, prev->proxy_protocol_tlvs, NULL); + ngx_conf_merge_value(conf->proxy_protocol_crc32c, + prev->proxy_protocol_crc32c, 0); + ngx_conf_merge_ptr_value(conf->local, prev->local, NULL); ngx_conf_merge_value(conf->socket_keepalive,