Tests: add PROXY protocol v2 passthrough tests.

Covers five scenarios for proxy_protocol_passthrough in the stream
module:

    1. specific types: only listed types forwarded; others absent
    2. all mode: every incoming TLV type forwarded except CRC32c
    3. suppression beats passthrough: proxy_protocol_tlv X "" prevents
       passthrough of type X even when X is in the passthrough list
    4. explicit value beats passthrough: user-configured value wins
    5. no incoming PP v2 header: passthrough silently skips

CRC32c config-time rejection and SSL sub-TLV name rejection are verified
manually via nginx -t; they are not included as automated tests because
they require nginx to fail to start, which is outside normal test flow.
This commit is contained in:
Vadim Zhestikov 2026-05-12 16:09:25 -07:00
parent 87111f4637
commit ec7c7e9ff4

View file

@ -0,0 +1,169 @@
#!/usr/bin/perl
# (C) Nginx, Inc.
# Tests for proxy_protocol_passthrough directive in the stream proxy module.
# An upstream stream_return backend echoes received TLV values as text,
# allowing the test to verify what nginx forwarded.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
use Test::Nginx::Stream qw/ stream /;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/stream/)->plan(13)
->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
stream {
%%TEST_GLOBALS_STREAM%%
# Backend: echoes authority, unique_id, custom TLV, ALPN values.
server {
listen 127.0.0.1:%%PORT_8090%% proxy_protocol;
return "auth:$proxy_protocol_tlv_authority
uid:$proxy_protocol_tlv_unique_id
cust:$proxy_protocol_tlv_0xae
alpn:$proxy_protocol_tlv_alpn
";
}
# S1: passthrough specific types. Only authority and unique_id are
# forwarded; ALPN and custom 0xae must be absent.
server {
listen 127.0.0.1:%%PORT_8081%% proxy_protocol;
proxy_pass 127.0.0.1:%%PORT_8090%%;
proxy_protocol on;
proxy_protocol_version 2;
proxy_protocol_passthrough authority unique_id;
}
# S2: passthrough all — forwards every incoming type except CRC32c.
server {
listen 127.0.0.1:%%PORT_8082%% proxy_protocol;
proxy_pass 127.0.0.1:%%PORT_8090%%;
proxy_protocol on;
proxy_protocol_version 2;
proxy_protocol_passthrough all;
}
# S3: suppression beats passthrough. AUTHORITY is in the passthrough
# list but is suppressed by proxy_protocol_tlv authority "".
server {
listen 127.0.0.1:%%PORT_8083%% proxy_protocol;
proxy_pass 127.0.0.1:%%PORT_8090%%;
proxy_protocol on;
proxy_protocol_version 2;
proxy_protocol_passthrough authority unique_id;
proxy_protocol_tlv authority "";
}
# S4: explicit value beats passthrough.
server {
listen 127.0.0.1:%%PORT_8084%% proxy_protocol;
proxy_pass 127.0.0.1:%%PORT_8090%%;
proxy_protocol on;
proxy_protocol_version 2;
proxy_protocol_passthrough authority;
proxy_protocol_tlv authority "override.example";
}
# S5: no incoming PP v2 header — passthrough silently skips.
server {
listen 127.0.0.1:%%PORT_8085%%;
proxy_pass 127.0.0.1:%%PORT_8090%%;
proxy_protocol on;
proxy_protocol_version 2;
proxy_protocol_passthrough all;
}
}
EOF
$t->run();
$t->waitforsocket('127.0.0.1:' . port(8085));
###############################################################################
# Helpers to build a raw PP v2 packet.
sub pp2_tlv {
my ($type, $value) = @_;
return pack('Cn', $type, length($value)) . $value;
}
sub pp2_header {
my ($tlvs) = @_;
my $sig = "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a";
my $addr = pack('NNnn', 0xc0000201, 0xc0000202, 1000, 8081);
return $sig . "\x21\x11" . pack('n', 12 + length($tlvs)) . $addr . $tlvs;
}
###############################################################################
# Build incoming TLVs: authority, unique_id, ALPN, and custom 0xae.
my $incoming = pp2_tlv(0x02, 'from-upstream.example')
. pp2_tlv(0x05, 'req-id-001')
. pp2_tlv(0x01, 'h2')
. pp2_tlv(0xae, 'vendor-data');
my $pkt = pp2_header($incoming);
# --- 1. Specific type passthrough ---
my $r = stream('127.0.0.1:' . port(8081))->io($pkt);
like($r, qr/auth:from-upstream\.example/, 'specific: authority forwarded');
like($r, qr/uid:req-id-001/, 'specific: unique_id forwarded');
unlike($r, qr/alpn:h2/, 'specific: ALPN not forwarded');
unlike($r, qr/cust:vendor-data/, 'specific: 0xae not forwarded');
# --- 2. All mode ---
$r = stream('127.0.0.1:' . port(8082))->io($pkt);
like($r, qr/auth:from-upstream\.example/, 'all: authority forwarded');
like($r, qr/uid:req-id-001/, 'all: unique_id forwarded');
like($r, qr/alpn:h2/, 'all: ALPN forwarded');
like($r, qr/cust:vendor-data/, 'all: custom 0xae forwarded');
# --- 3. Suppression beats passthrough ---
$r = stream('127.0.0.1:' . port(8083))->io($pkt);
unlike($r, qr/auth:\S/, 'suppress: AUTHORITY absent');
like($r, qr/uid:req-id-001/, 'suppress: unique_id still forwarded');
# --- 4. Explicit value beats passthrough ---
$r = stream('127.0.0.1:' . port(8084))->io($pkt);
like($r, qr/auth:override\.example/, 'explicit: override value used');
unlike($r, qr/from-upstream/, 'explicit: incoming value not used');
# --- 5. No incoming PP v2 header (plain TCP downstream) ---
$r = stream('127.0.0.1:' . port(8085))->io('');
unlike($r, qr/auth:\S|uid:\S|alpn:\S/, 'no-pp: passthrough silent-skips');
###############################################################################