Files
Archive/sing-box/common/tlsspoof/integration_windows_test.go
T
2026-04-17 21:15:10 +02:00

140 lines
3.8 KiB
Go

//go:build windows && (amd64 || 386)
package tlsspoof
import (
"encoding/hex"
"io"
"net"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func newSpoofer(t *testing.T, conn net.Conn, method Method) Spoofer {
t.Helper()
spoofer, err := NewSpoofer(conn, method)
require.NoError(t, err)
return spoofer
}
// Basic lifecycle: opening a spoofer against a live TCP conn installs
// the driver, spawns run(), then shuts down cleanly without ever
// injecting. Exercises the close path that cancels an in-flight Recv.
func TestIntegrationSpooferOpenClose(t *testing.T) {
listener, err := net.Listen("tcp4", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { listener.Close() })
accepted := make(chan net.Conn, 1)
go func() {
c, _ := listener.Accept()
accepted <- c
}()
client, err := net.Dial("tcp4", listener.Addr().String())
require.NoError(t, err)
t.Cleanup(func() { client.Close() })
server := <-accepted
t.Cleanup(func() {
if server != nil {
server.Close()
}
})
spoofer := newSpoofer(t, client, MethodWrongSequence)
require.NoError(t, spoofer.Close())
}
// End-to-end: Conn.Write injects a fake ClientHello with a fresh SNI, then
// forwards the real ClientHello. With wrong-sequence, the fake lands before
// the connection's send-next sequence — the peer TCP stack treats it as
// already-received and only surfaces the real bytes to the echo server.
func TestIntegrationConnInjectsThenForwardsRealCH(t *testing.T) {
listener, err := net.Listen("tcp4", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { listener.Close() })
serverReceived := make(chan []byte, 1)
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer conn.Close()
_ = conn.SetReadDeadline(time.Now().Add(5 * time.Second))
got, _ := io.ReadAll(conn)
serverReceived <- got
}()
client, err := net.Dial("tcp4", listener.Addr().String())
require.NoError(t, err)
t.Cleanup(func() { client.Close() })
spoofer := newSpoofer(t, client, MethodWrongSequence)
wrapped, err := NewConn(client, spoofer, "letsencrypt.org")
require.NoError(t, err)
payload, err := hex.DecodeString(realClientHello)
require.NoError(t, err)
n, err := wrapped.Write(payload)
require.NoError(t, err)
require.Equal(t, len(payload), n)
_ = wrapped.Close()
select {
case got := <-serverReceived:
require.Equal(t, payload, got,
"server must receive real ClientHello unchanged (wrong-sequence fake must be dropped)")
case <-time.After(5 * time.Second):
t.Fatal("echo server did not receive real ClientHello within 5s")
}
}
// Inject before any kernel payload: stages the fake, then Write flushes
// the real CH. Same terminal expectation as the Conn variant but via the
// Spoofer primitive directly.
func TestIntegrationSpooferInjectThenWrite(t *testing.T) {
listener, err := net.Listen("tcp4", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { listener.Close() })
serverReceived := make(chan []byte, 1)
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer conn.Close()
_ = conn.SetReadDeadline(time.Now().Add(5 * time.Second))
got, _ := io.ReadAll(conn)
serverReceived <- got
}()
client, err := net.Dial("tcp4", listener.Addr().String())
require.NoError(t, err)
t.Cleanup(func() { client.Close() })
spoofer := newSpoofer(t, client, MethodWrongSequence)
t.Cleanup(func() { spoofer.Close() })
fake, err := buildFakeClientHello("letsencrypt.org")
require.NoError(t, err)
require.NoError(t, spoofer.Inject(fake))
payload, err := hex.DecodeString(realClientHello)
require.NoError(t, err)
n, err := client.Write(payload)
require.NoError(t, err)
require.Equal(t, len(payload), n)
_ = client.Close()
select {
case got := <-serverReceived:
require.Equal(t, payload, got)
case <-time.After(5 * time.Second):
t.Fatal("echo server did not receive real ClientHello within 5s")
}
}