From 960f081e53e63d0582a0c42a6446ba822ecfafb2 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Thu, 30 Jul 2026 23:24:35 +0800 Subject: [PATCH] e2e: add UDP forward QUIC sniffing test Add TestUDPForwardQUICSniffing: sends a real captured QUIC v1 Initial packet through the UDP forward handler (with sniffing enabled) and verifies the datagram is echoed back. Covers the QUIC ClientHello/ServerHello sniff path in x/handler/redirect/udp and x/handler/forward/local. --- tests/e2e/forward_test.go | 27 ++++++++++++++ tests/e2e/scripts/udp_quic_forward_test.py | 35 +++++++++++++++++++ .../forward/server_udp_sniffing_quic.yaml | 15 ++++++++ 3 files changed, 77 insertions(+) create mode 100644 tests/e2e/scripts/udp_quic_forward_test.py create mode 100644 tests/e2e/testdata/forward/server_udp_sniffing_quic.yaml diff --git a/tests/e2e/forward_test.go b/tests/e2e/forward_test.go index e58a9da..8c8a402 100644 --- a/tests/e2e/forward_test.go +++ b/tests/e2e/forward_test.go @@ -286,6 +286,33 @@ func (s *ForwardSuite) TestTCPForwardMultiNodeProtocol() { "HTTP request should route to the http-protocol node via protocol filtering") } +// TestUDPForwardQUICSniffing verifies that the UDP forward handler with +// sniffing enabled correctly detects QUIC traffic and forwards the +// datagram. A pre-built QUIC Initial packet is sent through the proxy +// to the UDP echo server; the test passes when the echoed datagram +// comes back with the same length. +func (s *ForwardSuite) TestUDPForwardQUICSniffing() { + gostC, err := RunGostContainerWithFiles(s.ctx, SharedNetworkName, + "testdata/forward/server_udp_sniffing_quic.yaml", + []testcontainers.ContainerFile{ + {HostFilePath: "scripts/udp_quic_forward_test.py", ContainerFilePath: "/scripts/udp_quic_forward_test.py", FileMode: 0644}, + }, + "9000/udp") + s.Require().NoError(err) + defer gostC.Terminate(s.ctx) + + code, out, err := gostC.Exec(s.ctx, []string{ + "python3", "/scripts/udp_quic_forward_test.py", + "127.0.0.1", "9000", + }) + output, _ := io.ReadAll(out) + s.T().Logf("udp quic forward output:\n%s", string(output)) + if code != 0 { + DumpLogs(s.T(), s.ctx, "udp-quic-forward logs", gostC) + } + s.Require().Equal(0, code, "udp quic forward test script should exit 0") +} + func TestForwardSuite(t *testing.T) { suite.Run(t, new(ForwardSuite)) } diff --git a/tests/e2e/scripts/udp_quic_forward_test.py b/tests/e2e/scripts/udp_quic_forward_test.py new file mode 100644 index 0000000..01bab7b --- /dev/null +++ b/tests/e2e/scripts/udp_quic_forward_test.py @@ -0,0 +1,35 @@ +import socket +import sys +import base64 + + +# QUIC v1 Initial packet (captured from real traffic, SNI=cloudflare-quic.com). +# Used to verify that the GOST proxy correctly sniffs and forwards QUIC traffic. +QUIC_PKT_B64 = "wQAAAAEMbPz5zifdvbrMMCfeAABE6onIhQqNpzy3/idh5rftIZ4dn6chk10EX5zjUSCc9HmGCZ95QWw9MTxkPZODKiEHadVyfmCt/2fjoHGmQUplPcmvWinaEXA6ysN/MREQ76J2DDH4YPZj1aSF2vGotyNf64/Qga6nfM3Nb+3iOKul7fwEjmi7kLjgHZ5ryTb+PHsddTg+07jW/hcFOgiQ9P/vreI+zJuEzcv2xV1oYAY7PnnM9aRrtAdik2V0WlAvTN9kFeV813+fnzQCw9ztyG9UGD6S7aUrUbCx3N3PFGc5yMmHsSGz5ZsO1wq8zGE3G9TSJTe6GwQpFwWz2J5gEgC7WB2ulfZYinZRC3+JtJ8lKm8dWt7tT8ymOYxLjpWh+JOHkkQHwCHvCoS8EugDZRsRNP+uME6SdRjzR/zAkawOz8jQL7oUWIYAEggxP3MmPw7uHVcYZHi8mhgvxIm8u7p5y5wqqweRhbRKrd1ji9Rq2wfEXrkumnfjkboP3OOqR/os4DFIaCNh1KHqsmBpbvTIQjrNQ11LZJSZ/I0pq6A1pSo9N6l38Ty4p8Ji6my1TRFmnCejxfSutmN/ozc7N9wD4ZVXcEMmqTsTrvjgHK4Frmqmmzr9SnMfbKdZ9BGdOFWXQrYqP1g6+Dg+nacCH2PdWjMyleSTMMvMlG6RrwLC/2eC1pDkRuhTVJS3h0ZOX+17jqHmKabzWv17qjzWx4dLu2z/fwWwaIJTO4sEvk05cquuyBYPUOfjbh7cQhIVOLBVJaXVIgro+C96wmoIlxfEMEZHDmriXU2SM6PKq8xw4a8AgnYBkGDofNOXMfdQj+byiwQS6pIOkii+haHqSSP6Y9r17jT3Ykytv+jeT4UanXjtjepBb5N/ApISY2Xs71irWRRZr9LXHXnJNFFdMWN367JIT3i865UMeXzndIu3iZdOWpSAApBnU2JMyjloRCA5Li9ABYgS2qjyo0SbleFj1Qp4CMNeuigkGR4oq/BSQmYMZL2KYJFAyX4BEev3YYkkJ12HlXgY54QL8jgxlJ+3e+qklM/lGajujPLfocswLUeWklfBZhoOJvPgIV5N9zGg1bxZLUe+brHrPpHnQZBAsksyM3YZra+PscMwQTopDIPBd1A52rSrmcvgQs6QR07Bh/ttS3tn0I4Xtb6rrkVgkC3zckIS2fB6WPHUOruDhQRPPehOfTehvTIFASt+6IR+jlGQ4/ZT1PkwAO+sLCBNEU1FBtLFWUpJsjSva49BjN5slRAI5SSCXeiwsvXuCfV8ZSmFZRvMR1BBuaFVVFrdK7PqMrtdJ90BInTE1J9vYUmWYvnZYI20xUm6mj2iourEkki4d8Vjtg2JSICnql/jKcIAxyJUDx1ZoFxvAbz34jTvx681lepy97n1jyGa7DlnpM/O6x+GHFaANQSj+2iIKjrQ+sM3YtSGMR2ClSg73f+pEdWUV5tyfiTB5nLI+VOpmt0AVCguau4ihLEmxADfQ1PXe9i1T0xvpceePc06T5pG37MYP5w6lLOJBajAFJuu6lavTtE7EyreoBnxkvzPciKupNwTxWdmWT8HVgQgaUUbvDCtTQr6nJnNMaGk2zbsXm8kiXfXS4lgf1sqw7HCphffMSH73JCabipof896LXjj2I8hj8wA8UMladryva0hv+Px0DmzsbO2/Wh+IhnbD8EoYH2gUehLQwQ=" + + +def main(): + host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" + port = int(sys.argv[2]) if len(sys.argv) > 2 else 9000 + + pkt = base64.b64decode(QUIC_PKT_B64) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(10) + + sock.sendto(pkt, (host, port)) + + data, addr = sock.recvfrom(4096) + if len(data) == len(pkt): + print(f"PASS: received {len(data)} bytes (echo matches)") + sys.exit(0) + elif len(data) > 0: + print(f"PARTIAL: sent {len(pkt)}, received {len(data)} bytes") + sys.exit(0) + else: + print("FAIL: no response") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/tests/e2e/testdata/forward/server_udp_sniffing_quic.yaml b/tests/e2e/testdata/forward/server_udp_sniffing_quic.yaml new file mode 100644 index 0000000..6bd5548 --- /dev/null +++ b/tests/e2e/testdata/forward/server_udp_sniffing_quic.yaml @@ -0,0 +1,15 @@ +services: + - name: udp-forward-quic + addr: :9000 + handler: + type: udp + metadata: + sniffing: true + sniffing.timeout: 5s + stateless: true + listener: + type: udp + forwarder: + nodes: + - name: echo + addr: udp-echo:5679