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.
This commit is contained in:
Vadim Zhestikov 2026-04-16 12:07:14 -07:00
parent f78f953a56
commit dd7da8fb7c
3 changed files with 90 additions and 0 deletions

View file

@ -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)
{

View file

@ -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);

View file

@ -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,