mirror of
https://github.com/go-gost/gost.git
synced 2026-10-08 12:05:46 +00:00
- 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
116 lines
3.4 KiB
Go
116 lines
3.4 KiB
Go
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))
|
|
}
|