From c7ea19ec6699185667e6bc3b69ed02f774506950 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Mon, 13 Jul 2026 17:28:28 +0800 Subject: [PATCH] test(e2e): add bypass module e2e test suite Verify both blacklist and whitelist bypass modes: a matching destination skips the dead chain node and connects directly to the echo server, while a non-matching destination is forced through the dead node and never reaches it. --- tests/e2e/bypass_test.go | 116 +++++++++++++++++++++++ tests/e2e/testdata/bypass/blacklist.yaml | 29 ++++++ tests/e2e/testdata/bypass/whitelist.yaml | 32 +++++++ 3 files changed, 177 insertions(+) create mode 100644 tests/e2e/bypass_test.go create mode 100644 tests/e2e/testdata/bypass/blacklist.yaml create mode 100644 tests/e2e/testdata/bypass/whitelist.yaml diff --git a/tests/e2e/bypass_test.go b/tests/e2e/bypass_test.go new file mode 100644 index 0000000..b1f8b03 --- /dev/null +++ b/tests/e2e/bypass_test.go @@ -0,0 +1,116 @@ +package e2e + +import ( + "context" + "fmt" + "io" + "os" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/testcontainers/testcontainers-go" +) + +type BypassSuite struct { + suite.Suite + ctx context.Context + echoC testcontainers.Container + echoIP string +} + +func (s *BypassSuite) SetupSuite() { + s.ctx = context.Background() + + 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 *BypassSuite) TearDownSuite() { + if s.echoC != nil { + s.echoC.Terminate(s.ctx) + } +} + +// proxyRequest sends one request through the gost proxy and returns the +// response body. A bypassed (direct) request that reaches the echo server +// returns "hello-gost"; a request forced through the dead chain node returns +// a 503 and no echo body. +func (s *BypassSuite) proxyRequest(gostC testcontainers.Container, port, target string) string { + cmd := []string{ + "curl", "-s", + "-x", fmt.Sprintf("http://127.0.0.1:%s", port), + target, + } + _, out, err := gostC.Exec(s.ctx, cmd) + s.Require().NoError(err) + + body, err := io.ReadAll(out) + s.Require().NoError(err) + return string(body) +} + +// TestBlacklistBypassDirect verifies that a destination matching a blacklist +// bypass rule skips the (dead) chain node and connects directly to the echo +// server, so the request succeeds despite the dead node. +func (s *BypassSuite) TestBlacklistBypassDirect() { + cfg, err := RenderConfig("testdata/bypass/blacklist.yaml", ConfigData{ServerAddr: s.echoIP}) + s.Require().NoError(err) + defer os.Remove(cfg) + + gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, cfg, "8080/tcp") + s.Require().NoError(err) + defer gostC.Terminate(s.ctx) + + body := s.proxyRequest(gostC, "8080", fmt.Sprintf("http://%s:5678", s.echoIP)) + s.Require().Contains(body, "hello-gost") +} + +// TestBlacklistBypassViaChain verifies that a destination NOT matching the +// blacklist bypass is routed through the chain node. With the node dead the +// request never reaches the echo server, confirming the prior success was due +// to the bypass. +func (s *BypassSuite) TestBlacklistBypassViaChain() { + cfg, err := RenderConfig("testdata/bypass/blacklist.yaml", ConfigData{ServerAddr: s.echoIP}) + s.Require().NoError(err) + defer os.Remove(cfg) + + gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, cfg, "8080/tcp") + s.Require().NoError(err) + defer gostC.Terminate(s.ctx) + + body := s.proxyRequest(gostC, "8080", "http://10.0.0.1:5678") + s.Require().NotContains(body, "hello-gost") +} + +// TestWhitelistBypassDirect verifies that a destination outside the whitelist +// rule is bypassed (connects directly) and reaches the echo server even though +// the chain node is dead. +func (s *BypassSuite) TestWhitelistBypassDirect() { + gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, "testdata/bypass/whitelist.yaml", "8080/tcp") + s.Require().NoError(err) + defer gostC.Terminate(s.ctx) + + body := s.proxyRequest(gostC, "8080", fmt.Sprintf("http://%s:5678", s.echoIP)) + s.Require().Contains(body, "hello-gost") +} + +// TestWhitelistBypassViaChain verifies that a destination matching the +// whitelist rule is forced through the chain node; with the node dead the +// request never reaches the echo server. +func (s *BypassSuite) TestWhitelistBypassViaChain() { + gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, "testdata/bypass/whitelist.yaml", "8080/tcp") + s.Require().NoError(err) + defer gostC.Terminate(s.ctx) + + body := s.proxyRequest(gostC, "8080", "http://10.0.0.1:5678") + s.Require().NotContains(body, "hello-gost") +} + +func TestBypassSuite(t *testing.T) { + suite.Run(t, new(BypassSuite)) +} diff --git a/tests/e2e/testdata/bypass/blacklist.yaml b/tests/e2e/testdata/bypass/blacklist.yaml new file mode 100644 index 0000000..39a06a5 --- /dev/null +++ b/tests/e2e/testdata/bypass/blacklist.yaml @@ -0,0 +1,29 @@ +# Forward proxy whose only chain node is dead (127.0.0.1:18082). A blacklist +# bypass matching the destination skips that node and connects directly to the +# echo server; a non-matching destination is forced through the dead node. +services: + - name: proxy + addr: ":8080" + handler: + type: http + chain: my-chain + listener: + type: tcp + +chains: + - name: my-chain + hops: + - name: hop-0 + nodes: + - name: node-0 + addr: 127.0.0.1:18082 + bypass: bypass-0 + connector: + type: http + dialer: + type: tcp + +bypasses: + - name: bypass-0 + matchers: + - "{{.ServerAddr}}" diff --git a/tests/e2e/testdata/bypass/whitelist.yaml b/tests/e2e/testdata/bypass/whitelist.yaml new file mode 100644 index 0000000..1265079 --- /dev/null +++ b/tests/e2e/testdata/bypass/whitelist.yaml @@ -0,0 +1,32 @@ +# Forward proxy whose only chain node is dead (127.0.0.1:18082). A whitelist +# bypass inverts the logic: only destinations matching the rule are forced +# through the chain. The echo server's address is outside 10.0.0.0/8, so it is +# bypassed (connected directly); a 10.0.0.0/8 destination is forced through the +# dead node. +services: + - name: proxy + addr: ":8080" + handler: + type: http + chain: my-chain + listener: + type: tcp + +chains: + - name: my-chain + hops: + - name: hop-0 + nodes: + - name: node-0 + addr: 127.0.0.1:18082 + bypass: bypass-0 + connector: + type: http + dialer: + type: tcp + +bypasses: + - name: bypass-0 + whitelist: true + matchers: + - "10.0.0.0/8"