diff options
| author | EuAndreh <eu@euandre.org> | 2026-04-30 14:50:53 -0300 |
|---|---|---|
| committer | EuAndreh <eu@euandre.org> | 2026-04-30 14:50:53 -0300 |
| commit | 543a43386b472049367ee81857209c13bc394e98 (patch) | |
| tree | 9db2ee5ac825f9b715900dc09168d21f606b42c3 /tests | |
| parent | src/wscat.go: Make Start a duplex multi-message relay (diff) | |
| download | wscat-543a43386b472049367ee81857209c13bc394e98.tar.gz wscat-543a43386b472049367ee81857209c13bc394e98.tar.xz | |
Forward PROXY v2 AUTHORITY end-to-end
wscat now sits transparently in the untls → wscat → papod path:
on Accept, it parses any PROXY v2 header from upstream and stashes
the AUTHORITY TLV on the wrapped conn; on the downstream dial to
papod, it re-emits a PROXY v2 header with the same authority
before forwarding any WebSocket payload bytes.
A 5-second read deadline caps the parse window (so a slowloris
peer can't pin the accept goroutine), and Accept loops past
per-connection wrap errors — without that, an "nc -z" liveness
probe (open + close, zero bytes) bubbles up as EOF to
http.Server.Serve and panics the whole process.
When no PROXY header is present (e.g. the integration test stack
where binder speaks raw bytes) wscat skips the re-emit and lets
papod fall back to PAPOD_NETWORK_NAME.
Tests cover header build/parse roundtrip, the no-signature-pass-
through case, and header byte layout.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/wscat.go | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/tests/wscat.go b/tests/wscat.go index 25fce9e..f21c4b0 100644 --- a/tests/wscat.go +++ b/tests/wscat.go @@ -1733,6 +1733,66 @@ func TestParseExtensions(t *testing.T) { } } +func TestProxyV2Roundtrip(t *testing.T) { + hdr := proxyV2Header("papo.example.com") + if len(hdr) != 35 { + t.Fatalf("header length = %d, want 35", len(hdr)) + } + tail := []byte("GET / HTTP/1.1\r\nHost: x\r\n\r\n") + stream := append(hdr, tail...) + + br := bufio.NewReader(bytes.NewReader(stream)) + authority, err := parseProxyV2Authority(br) + if err != nil { + t.Fatalf("parse error: %v", err) + } + if authority != "papo.example.com" { + t.Errorf("authority = %q, want papo.example.com", authority) + } + + rest, err := io.ReadAll(br) + if err != nil { + t.Fatalf("ReadAll: %v", err) + } + if !bytes.Equal(rest, tail) { + t.Errorf("rest = %q, want %q", rest, tail) + } +} + +func TestProxyV2NoSignatureLeavesStream(t *testing.T) { + tail := []byte("GET / HTTP/1.1\r\n") + br := bufio.NewReader(bytes.NewReader(tail)) + authority, err := parseProxyV2Authority(br) + if err != nil { + t.Fatalf("err = %v, want nil", err) + } + if authority != "" { + t.Errorf("authority = %q, want empty", authority) + } + rest, _ := io.ReadAll(br) + if !bytes.Equal(rest, tail) { + t.Errorf("non-PROXY bytes consumed: rest = %q", rest) + } +} + +func TestProxyV2HeaderShape(t *testing.T) { + hdr := proxyV2Header("x") + if hdr[12] != 0x21 { + t.Errorf("ver_cmd = %#x, want 0x21", hdr[12]) + } + if hdr[13] != 0x00 { + t.Errorf("fam = %#x, want 0x00 (AF_UNSPEC)", hdr[13]) + } + // AUTHORITY TLV at offset 16: type=0x02, length=0x0001, value="x" + if hdr[16] != _pp2TypeAuthority { + t.Errorf("tlv type = %#x, want %#x", + hdr[16], _pp2TypeAuthority) + } + if hdr[19] != 'x' { + t.Errorf("tlv value byte = %#x", hdr[19]) + } +} + func test_parseArgs() { given := parseArgs([]string { "x", "y", "z" }) expected := _CLIArgs { @@ -1788,6 +1848,9 @@ func MainTest() { { "TestTokenListContainsValue", TestTokenListContainsValue }, { "TestIsValidChallengeKey", TestIsValidChallengeKey }, { "TestParseExtensions", TestParseExtensions }, + { "TestProxyV2Roundtrip", TestProxyV2Roundtrip }, + { "TestProxyV2NoSignatureLeavesStream", TestProxyV2NoSignatureLeavesStream }, + { "TestProxyV2HeaderShape", TestProxyV2HeaderShape }, } benchmarks := []testing.InternalBenchmark { |
