mirror of
https://github.com/go-gost/gost.git
synced 2026-10-08 03:55:45 +00:00
test(e2e): add QUIC cipherKey invalid-datagram regression
Add an e2e suite covering go-gost/x#125: a malformed UDP datagram sent to a cipherKey-enabled QUIC listener must not close the shared transport. Uses the canonical http-over-quic chaining pattern and verifies the forward still works after the invalid datagram. Also bumps go.mod/go.sum (plugin v0.6.1, otel, x/net, etc.).
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
)
|
||||
|
||||
// QUICSuite covers the QUIC listener/dialer pair (listener type: quic, dialer
|
||||
// type: quic) with the cipherKey obfuscation enabled. It uses the canonical
|
||||
// GOST chaining pattern: a client container exposes a plain HTTP proxy that
|
||||
// tunnels through a quic chain to a quic server (HTTP over QUIC).
|
||||
type QUICSuite struct {
|
||||
suite.Suite
|
||||
ctx context.Context
|
||||
echoC testcontainers.Container
|
||||
echoIP string
|
||||
}
|
||||
|
||||
func (s *QUICSuite) SetupSuite() {
|
||||
s.ctx = context.Background()
|
||||
|
||||
s.T().Logf("start tcp echo container...")
|
||||
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
|
||||
s.Require().NoError(err)
|
||||
s.echoC = echoC
|
||||
|
||||
echoIP, err := echoC.ContainerIP(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
s.echoIP = echoIP
|
||||
}
|
||||
|
||||
func (s *QUICSuite) TearDownSuite() {
|
||||
if s.echoC != nil {
|
||||
s.echoC.Terminate(s.ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// startChain brings up a quic server (alias "quic-server", cipherKey enabled,
|
||||
// http handler) and a client container that chains to it through a quic
|
||||
// dialer. The client exposes port 8080 for curl.
|
||||
func (s *QUICSuite) startChain() (testcontainers.Container, testcontainers.Container) {
|
||||
s.T().Helper()
|
||||
|
||||
serverC, err := RunGostContainerWithOptions(s.ctx, SharedNetworkName,
|
||||
"testdata/quic/server.yaml", []string{"quic-server"}, []string{"18843/udp"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
rendered, err := RenderConfig("testdata/quic/client.yaml", ConfigData{ServerAddr: "quic-server:18843"})
|
||||
s.Require().NoError(err)
|
||||
s.T().Cleanup(func() { os.Remove(rendered) })
|
||||
|
||||
clientC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, rendered, "8080/tcp")
|
||||
s.Require().NoError(err)
|
||||
|
||||
return serverC, clientC
|
||||
}
|
||||
|
||||
// curlEcho runs curl through the client's local http proxy and returns the
|
||||
// exit code plus the captured body.
|
||||
func (s *QUICSuite) curlEcho(clientC testcontainers.Container) (int, string) {
|
||||
s.T().Helper()
|
||||
cmd := []string{"curl", "-s", "--max-time", "20", "-x", "http://127.0.0.1:8080",
|
||||
fmt.Sprintf("http://%s:5678/", s.echoIP)}
|
||||
code, out, _ := clientC.Exec(s.ctx, cmd)
|
||||
body, _ := io.ReadAll(out)
|
||||
return code, string(body)
|
||||
}
|
||||
|
||||
// sendInvalidDatagram sends a single malformed (non-encrypted) UDP datagram to
|
||||
// the quic server's listener, reproducing the issue's repro packet.
|
||||
func (s *QUICSuite) sendInvalidDatagram(clientC testcontainers.Container) {
|
||||
s.T().Helper()
|
||||
cmd := []string{"python3", "-c",
|
||||
"import socket; s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.sendto(b\"x\", (\"quic-server\", 18843))"}
|
||||
code, out, err := clientC.Exec(s.ctx, cmd)
|
||||
s.Require().NoError(err)
|
||||
if code != 0 {
|
||||
b, _ := io.ReadAll(out)
|
||||
s.T().Fatalf("send invalid datagram failed (%d): %s", code, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
// TestQUICForwardCipherKeySurvivesInvalidDatagram verifies that a malformed
|
||||
// UDP datagram does not close the shared QUIC transport (go-gost/x#125).
|
||||
//
|
||||
// With the bug, cipherConn.ReadFrom returned the decrypt error straight to
|
||||
// quic-go, which treats any packet-socket error as fatal and closes the shared
|
||||
// transport: a single invalid datagram kills the listener and correct clients
|
||||
// subsequently time out. With the fix the invalid datagram is discarded and
|
||||
// the listener keeps serving.
|
||||
//
|
||||
// Sequence: baseline forward works -> malformed datagram -> forward still works.
|
||||
func (s *QUICSuite) TestQUICForwardCipherKeySurvivesInvalidDatagram() {
|
||||
serverC, clientC := s.startChain()
|
||||
defer serverC.Terminate(s.ctx)
|
||||
defer clientC.Terminate(s.ctx)
|
||||
|
||||
// Baseline: a valid QUIC forward must work before we poke the listener.
|
||||
code, body := s.curlEcho(clientC)
|
||||
if code != 0 || !strings.Contains(body, "hello-gost") {
|
||||
s.dump("quic-baseline logs", clientC, serverC)
|
||||
}
|
||||
s.Require().Equal(0, code, "baseline QUIC forward should work")
|
||||
s.Require().Contains(body, "hello-gost")
|
||||
|
||||
// Send one malformed UDP datagram to the quic listener's cipher socket.
|
||||
s.sendInvalidDatagram(clientC)
|
||||
|
||||
// The listener must still be alive: a fresh QUIC forward must succeed.
|
||||
code, body = s.curlEcho(clientC)
|
||||
if code != 0 || !strings.Contains(body, "hello-gost") {
|
||||
s.dump("quic-after-invalid logs", clientC, serverC)
|
||||
}
|
||||
s.Require().Equal(0, code,
|
||||
"QUIC forward should still work after an invalid datagram (issue #125)")
|
||||
s.Require().Contains(body, "hello-gost",
|
||||
"listener must not close on an invalid datagram (issue #125)")
|
||||
}
|
||||
|
||||
func (s *QUICSuite) dump(label string, cs ...testcontainers.Container) {
|
||||
for _, c := range cs {
|
||||
DumpLogs(s.T(), s.ctx, fmt.Sprintf("%s: %s", label, c.GetContainerID()), c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQUICSuite(t *testing.T) {
|
||||
suite.Run(t, new(QUICSuite))
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
- name: http-proxy
|
||||
addr: :8080
|
||||
handler:
|
||||
type: http
|
||||
chain: quic-chain
|
||||
listener:
|
||||
type: tcp
|
||||
|
||||
chains:
|
||||
- name: quic-chain
|
||||
hops:
|
||||
- name: hop-0
|
||||
nodes:
|
||||
- name: node-0
|
||||
addr: {{.ServerAddr}}
|
||||
connector:
|
||||
type: http
|
||||
dialer:
|
||||
type: quic
|
||||
metadata:
|
||||
cipherKey: 0123456789abcdef0123456789abcdef
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
- name: quic-http
|
||||
addr: :18843
|
||||
handler:
|
||||
type: http
|
||||
listener:
|
||||
type: quic
|
||||
metadata:
|
||||
cipherKey: 0123456789abcdef0123456789abcdef
|
||||
Reference in New Issue
Block a user