f8ddb193cb
Split the 339-line handler monolith and 1169-line test blob into 11 files (5 source + 6 test) following the handler/forward/local/ pattern. Source files: handler.go (core), forward.go (raw forwarding), sniffing.go (SnifferBuilder + protocol dispatch), util.go (errors + recorder + rate limit), metadata.go (config parsing). Fixes: add sync.Mutex for hop access (race), use sentinel errors for errors.Is compatibility, add nil-addr guard in checkRateLimit, reuse SnifferBuilder via Build() instead of per-call construction.
158 lines
4.4 KiB
Go
158 lines
4.4 KiB
Go
package remote
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-gost/core/chain"
|
|
"github.com/go-gost/x/internal/util/sniffing"
|
|
xrecorder "github.com/go-gost/x/recorder"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SnifferBuilder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestSnifferBuilder(t *testing.T) {
|
|
h := newTestHandler()
|
|
h.sniffer.Websocket = true
|
|
h.sniffer.WebsocketSampleRate = 0.5
|
|
h.sniffer.ReadTimeout = 30 * time.Second
|
|
|
|
sniffer := h.sniffer.Build()
|
|
|
|
if !sniffer.Websocket {
|
|
t.Error("expected Websocket to be true")
|
|
}
|
|
if sniffer.WebsocketSampleRate != 0.5 {
|
|
t.Errorf("expected 0.5, got %f", sniffer.WebsocketSampleRate)
|
|
}
|
|
if sniffer.ReadTimeout != 30*time.Second {
|
|
t.Errorf("expected 30s, got %v", sniffer.ReadTimeout)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// sniffingDial
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestSniffingDial_Success(t *testing.T) {
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
h := newInitdHandler(withRouter(&mockRouter{
|
|
opts: &chain.RouterOptions{},
|
|
dialFn: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
return server, nil
|
|
},
|
|
}))
|
|
ro := &xrecorder.HandlerRecorderObject{}
|
|
|
|
cc, err := h.sniffingDial(context.Background(), "tcp", "example.com:80", ro)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
defer cc.Close()
|
|
|
|
if ro.Route == "" {
|
|
t.Log("route is empty (mock does not populate context buffer)")
|
|
}
|
|
}
|
|
|
|
func TestSniffingDial_RouterError(t *testing.T) {
|
|
expectedErr := errors.New("dial failed")
|
|
h := newInitdHandler(withRouter(&mockRouter{
|
|
opts: &chain.RouterOptions{},
|
|
dialFn: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
return nil, expectedErr
|
|
},
|
|
}))
|
|
ro := &xrecorder.HandlerRecorderObject{}
|
|
|
|
_, err := h.sniffingDial(context.Background(), "tcp", "example.com:80", ro)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if !errors.Is(err, expectedErr) {
|
|
t.Errorf("expected %v, got %v", expectedErr, err)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// handleSniffedProtocol
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestHandleSniffedProtocol_UnknownProto(t *testing.T) {
|
|
h := newInitdHandler()
|
|
conn := newStringConn(nil)
|
|
ro := &xrecorder.HandlerRecorderObject{}
|
|
|
|
handled, err := h.handleSniffedProtocol(context.Background(), conn, ro, nopLog(), "ssh")
|
|
if handled {
|
|
t.Error("expected handled=false for unknown proto")
|
|
}
|
|
if err != nil {
|
|
t.Errorf("expected nil error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHandleSniffedProtocol_EmptyProto(t *testing.T) {
|
|
h := newInitdHandler()
|
|
conn := newStringConn(nil)
|
|
ro := &xrecorder.HandlerRecorderObject{}
|
|
|
|
handled, err := h.handleSniffedProtocol(context.Background(), conn, ro, nopLog(), "")
|
|
if handled {
|
|
t.Error("expected handled=false for empty proto")
|
|
}
|
|
if err != nil {
|
|
t.Errorf("expected nil error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHandleSniffedProtocol_HTTP(t *testing.T) {
|
|
h := newInitdHandler(withRouter(&mockRouter{
|
|
opts: &chain.RouterOptions{},
|
|
dialFn: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
return nil, errors.New("dial not configured in test")
|
|
},
|
|
}))
|
|
conn := newStringConn([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
|
|
ro := &xrecorder.HandlerRecorderObject{}
|
|
|
|
handled, err := h.handleSniffedProtocol(context.Background(), conn, ro, nopLog(), sniffing.ProtoHTTP)
|
|
|
|
if !handled {
|
|
t.Error("expected handled=true for HTTP proto")
|
|
}
|
|
if err == nil {
|
|
t.Log("sniffer processed HTTP request without error")
|
|
}
|
|
}
|
|
|
|
func TestHandleSniffedProtocol_TLS(t *testing.T) {
|
|
h := newInitdHandler(withRouter(&mockRouter{
|
|
opts: &chain.RouterOptions{},
|
|
dialFn: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
return nil, errors.New("dial not configured in test")
|
|
},
|
|
}))
|
|
conn := newStringConn([]byte{
|
|
0x16, 0x03, 0x01, 0x00, 0x06, 0x01, 0x00, 0x00, 0x02, 0x03, 0x01,
|
|
})
|
|
ro := &xrecorder.HandlerRecorderObject{}
|
|
|
|
handled, err := h.handleSniffedProtocol(context.Background(), conn, ro, nopLog(), sniffing.ProtoTLS)
|
|
|
|
if !handled {
|
|
t.Error("expected handled=true for TLS proto")
|
|
}
|
|
if err == nil {
|
|
t.Log("sniffer processed TLS ClientHello without error")
|
|
}
|
|
}
|