Files
ginuerzh 7b088f8f42 feat(socks5): add Tor RESOLVE/RESOLVE_PTR extension command support (gost#524)
Adds SOCKS5 handler and connector support for Tor-specific RESOLVE (0xF0)
and RESOLVE_PTR (0xF1) commands, enabling tools like torsocks and
tor-resolve to work through GOST when forwarding to a Tor SOCKS5 proxy.

- Define CmdResolve (0xF0) and CmdResolvePTR (0xF1) in x/internal/util/socks
- Add ContextWithSocks5Cmd/Socks5CmdFromContext for per-request command override
- Connector reads context command, sends it upstream, returns resolveConn
- resolveConn wraps net.Conn and exposes resolved addr via Metadata()
- Handler dispatches 0xF0/0xF1 to handleResolve/handleResolvePTR
- Gate behind enableTor metadata (tor=true), reject with CmdUnsupported by default
- 9 unit tests across ctx/, connector/, handler/ packages
2026-07-16 21:10:13 +08:00

125 lines
2.7 KiB
Go

package v5
import (
"context"
"net"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
xlogger "github.com/go-gost/x/logger"
)
func testLogger() logger.Logger {
return xlogger.Nop()
}
func TestWriteSocksReply(t *testing.T) {
server, client := net.Pipe()
defer server.Close()
defer client.Close()
addr := &gosocks5.Addr{Type: gosocks5.AddrIPv4, Host: "127.0.0.1", Port: 9050}
done := make(chan error, 1)
go func() {
done <- writeSocksReply(server, gosocks5.Succeeded, addr)
}()
reply, err := gosocks5.ReadReply(client)
if err != nil {
t.Fatalf("ReadReply: %v", err)
}
if reply.Rep != gosocks5.Succeeded {
t.Errorf("Rep = %d, want %d", reply.Rep, gosocks5.Succeeded)
}
if reply.Addr == nil || reply.Addr.Host != "127.0.0.1" {
t.Errorf("Addr = %v, want 127.0.0.1", reply.Addr)
}
if err := <-done; err != nil {
t.Errorf("writeSocksReply error: %v", err)
}
}
func TestWriteSocksReply_CmdUnsupported(t *testing.T) {
server, client := net.Pipe()
defer server.Close()
defer client.Close()
done := make(chan error, 1)
go func() {
done <- writeSocksReply(server, gosocks5.CmdUnsupported, nil)
}()
reply, err := gosocks5.ReadReply(client)
if err != nil {
t.Fatalf("ReadReply: %v", err)
}
if reply.Rep != gosocks5.CmdUnsupported {
t.Errorf("Rep = %d, want %d", reply.Rep, gosocks5.CmdUnsupported)
}
if err := <-done; err != nil {
t.Errorf("writeSocksReply error: %v", err)
}
}
func TestHandleResolve_TorDisabled(t *testing.T) {
h := &socks5Handler{
md: metadata{enableTor: false},
}
h.options.Logger = testLogger()
server, client := net.Pipe()
defer server.Close()
defer client.Close()
done := make(chan error, 1)
go func() {
done <- h.handleResolve(context.Background(), server, "example.com:0", testLogger())
}()
reply, err := gosocks5.ReadReply(client)
if err != nil {
t.Fatalf("ReadReply: %v", err)
}
if reply.Rep != gosocks5.CmdUnsupported {
t.Errorf("Rep = %d, want CmdUnsupported (%d)", reply.Rep, gosocks5.CmdUnsupported)
}
err = <-done
if err == nil {
t.Error("expected error when tor is disabled, got nil")
}
}
func TestHandleResolvePTR_TorDisabled(t *testing.T) {
h := &socks5Handler{
md: metadata{enableTor: false},
}
h.options.Logger = testLogger()
server, client := net.Pipe()
defer server.Close()
defer client.Close()
done := make(chan error, 1)
go func() {
done <- h.handleResolvePTR(context.Background(), server, "192.168.1.1:0", testLogger())
}()
reply, err := gosocks5.ReadReply(client)
if err != nil {
t.Fatalf("ReadReply: %v", err)
}
if reply.Rep != gosocks5.CmdUnsupported {
t.Errorf("Rep = %d, want CmdUnsupported (%d)", reply.Rep, gosocks5.CmdUnsupported)
}
err = <-done
if err == nil {
t.Error("expected error when tor is disabled, got nil")
}
}