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

81 lines
2.3 KiB
Go

package v5
import (
"context"
"errors"
"net"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/socks"
)
// handleResolve handles a Tor SOCKS5 RESOLVE command (0xF0).
// It resolves a hostname to an IP address via the upstream Tor SOCKS5 proxy.
func (h *socks5Handler) handleResolve(ctx context.Context, conn net.Conn, address string, log logger.Logger) error {
if !h.md.enableTor {
return h.rejectCmd(conn, log)
}
ctx = xctx.ContextWithSocks5Cmd(ctx, socks.CmdResolve)
return h.doResolve(ctx, conn, address, log)
}
// handleResolvePTR handles a Tor SOCKS5 RESOLVE_PTR command (0xF1).
// It performs a reverse DNS lookup (IP to hostname) via the upstream Tor SOCKS5 proxy.
func (h *socks5Handler) handleResolvePTR(ctx context.Context, conn net.Conn, address string, log logger.Logger) error {
if !h.md.enableTor {
return h.rejectCmd(conn, log)
}
ctx = xctx.ContextWithSocks5Cmd(ctx, socks.CmdResolvePTR)
return h.doResolve(ctx, conn, address, log)
}
// rejectCmd writes a CmdUnsupported reply and returns ErrUnknownCmd.
func (h *socks5Handler) rejectCmd(conn net.Conn, log logger.Logger) error {
log.Error(ErrUnknownCmd)
resp := gosocks5.NewReply(gosocks5.CmdUnsupported, nil)
log.Trace(resp)
resp.Write(conn)
return ErrUnknownCmd
}
func (h *socks5Handler) doResolve(ctx context.Context, conn net.Conn, address string, log logger.Logger) error {
cc, err := h.options.Router.Dial(ctx, "tcp", address)
if err != nil {
log.Error(err)
writeSocksReply(conn, gosocks5.Failure, nil)
return err
}
defer cc.Close()
// Extract resolved address from the chain connection's metadata.
var resolvedAddr *gosocks5.Addr
if mdConn, ok := cc.(md.Metadatable); ok {
if v := mdConn.Metadata().Get("resolvedAddr"); v != nil {
resolvedAddr, _ = v.(*gosocks5.Addr)
}
}
if resolvedAddr == nil {
err = errors.New("socks5: no resolved address in reply")
log.Error(err)
writeSocksReply(conn, gosocks5.Failure, nil)
return err
}
log.Debugf("resolved %s -> %s", address, resolvedAddr)
resp := gosocks5.NewReply(gosocks5.Succeeded, resolvedAddr)
log.Trace(resp)
resp.Write(conn)
return nil
}
func writeSocksReply(conn net.Conn, rep uint8, addr *gosocks5.Addr) error {
resp := gosocks5.NewReply(rep, addr)
return resp.Write(conn)
}