mirror of
https://github.com/go-gost/gost.git
synced 2026-10-08 03:55:45 +00:00
chore: bump x to v0.15.1, add e2e tests for chainGroup and sniffing
- Bump github.com/go-gost/x v0.15.0 → v0.15.1 - ChainGroupSuite: matcher routing by host, probe dead-chain pre-marking - SniffingSuite: SOCKS5+HTTPS w/o SNI, debug-only log on empty SNI - Add RunHTTPSEchoContainer and https_echo.py test helper
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
)
|
||||
|
||||
// SniffingSuite covers protocol sniffing behavior.
|
||||
type SniffingSuite struct {
|
||||
suite.Suite
|
||||
ctx context.Context
|
||||
httpsEchoC testcontainers.Container
|
||||
httpsIP string
|
||||
}
|
||||
|
||||
func (s *SniffingSuite) SetupSuite() {
|
||||
s.ctx = context.Background()
|
||||
|
||||
c, err := RunHTTPSEchoContainer(s.ctx, SharedNetworkName)
|
||||
s.Require().NoError(err)
|
||||
s.httpsEchoC = c
|
||||
|
||||
ip, err := c.ContainerIP(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
s.httpsIP = ip
|
||||
}
|
||||
|
||||
func (s *SniffingSuite) TearDownSuite() {
|
||||
if s.httpsEchoC != nil {
|
||||
s.httpsEchoC.Terminate(s.ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// curlThroughSOCKS5 runs curl through the GOST SOCKS5 proxy on the given
|
||||
// container address and port, connecting to the HTTPS echo server by IP.
|
||||
// Connecting by IP makes curl omit the TLS SNI extension.
|
||||
func (s *SniffingSuite) curlThroughSOCKS5(c testcontainers.Container, proxyPort string) string {
|
||||
s.T().Helper()
|
||||
// curl -k: don't verify the self-signed cert on the echo server
|
||||
// curl -x socks5://...: route through GOST SOCKS5 proxy
|
||||
// Using the container IP as the host makes curl skip SNI entirely.
|
||||
cmd := []string{"curl", "-k", "-s",
|
||||
"-x", "socks5://127.0.0.1:" + proxyPort,
|
||||
"https://" + s.httpsIP + ":8443",
|
||||
}
|
||||
code, out, err := c.Exec(s.ctx, cmd)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Zero(code, "curl should exit 0")
|
||||
b, _ := io.ReadAll(out)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// TestSOCKS5NoSNI verifies that a SOCKS5 proxy with sniffing enabled can
|
||||
// successfully forward TLS connections that lack an SNI extension — such as
|
||||
// when a client connects to an HTTPS server by IP address.
|
||||
func (s *SniffingSuite) TestSOCKS5NoSNI() {
|
||||
gostC, err := RunGostContainer(s.ctx, SharedNetworkName,
|
||||
"testdata/sniffing/no_sni.yaml",
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
defer func() {
|
||||
DumpLogs(s.T(), s.ctx, "gost-no-sni", gostC)
|
||||
gostC.Terminate(s.ctx)
|
||||
}()
|
||||
|
||||
body := s.curlThroughSOCKS5(gostC, "8080")
|
||||
s.Require().Contains(body, "hello-gost",
|
||||
"HTTPS response should pass through when SNI is empty")
|
||||
}
|
||||
|
||||
// TestSOCKS5NoSNI_Callback verifies the same behavior a second time, serving
|
||||
// as a sanity check that state doesn't leak between connections.
|
||||
func (s *SniffingSuite) TestSOCKS5NoSNI_Callback() {
|
||||
s.TestSOCKS5NoSNI()
|
||||
}
|
||||
|
||||
// TestSOCKS5NoSNI_LogsOnlyDebug verifies the proxy logs a debug message
|
||||
// rather than an error when SNI is missing.
|
||||
func (s *SniffingSuite) TestSOCKS5NoSNI_LogsOnlyDebug() {
|
||||
gostC, err := RunGostContainer(s.ctx, SharedNetworkName,
|
||||
"testdata/sniffing/no_sni.yaml",
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
defer func() {
|
||||
// Do NOT dump logs before the grep — DumpLogs consumes the reader
|
||||
gostC.Terminate(s.ctx)
|
||||
}()
|
||||
|
||||
body := s.curlThroughSOCKS5(gostC, "8080")
|
||||
s.Require().Contains(body, "hello-gost")
|
||||
|
||||
// Verify the debug log message exists and no error-level SNI message.
|
||||
logs, err := gostC.Logs(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
defer logs.Close()
|
||||
logBody, _ := io.ReadAll(logs)
|
||||
logStr := string(logBody)
|
||||
|
||||
s.Require().Contains(logStr, "no sni in clienthello",
|
||||
"should log a debug message when SNI is empty")
|
||||
s.Require().NotContains(logStr, "tls: sni is empty",
|
||||
"must not error on empty SNI")
|
||||
// The log level for "no sni" should be debug, not error.
|
||||
s.Require().False(strings.Contains(logStr, `"level":"error"`),
|
||||
"should not log any error-level message")
|
||||
}
|
||||
|
||||
func TestSniffingSuite(t *testing.T) {
|
||||
suite.Run(t, new(SniffingSuite))
|
||||
}
|
||||
Reference in New Issue
Block a user