Files
tsnet-proxy/vendor/tailscale.com/net/socks5/socks5.go
T
2026-10-22 12:27:19 +01:00

813 lines
21 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package socks5 is a SOCKS5 server implementation.
//
// This is used for userspace networking in Tailscale. Specifically,
// this is used for dialing out of the machine to other nodes, without
// the host kernel's involvement, so it doesn't proper routing tables,
// TUN, IPv6, etc. This package is meant to only handle the SOCKS5 protocol
// details and not any integration with Tailscale internals itself.
//
// The glue between this package and Tailscale is in net/socks5/tssocks.
package socks5
import (
"bytes"
"context"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
"slices"
"strconv"
"time"
"tailscale.com/syncs"
"tailscale.com/types/logger"
"tailscale.com/types/nettype"
)
// Authentication METHODs described in RFC 1928, section 3.
const (
noAuthRequired byte = 0
passwordAuth byte = 2
noAcceptableAuth byte = 255
)
// passwordAuthVersion is the auth version byte described in RFC 1929.
const passwordAuthVersion = 1
// socks5Version is the byte that represents the SOCKS version
// in requests.
const socks5Version byte = 5
// commandType are the bytes sent in SOCKS5 packets
// that represent the kind of connection the client needs.
type commandType byte
// The set of valid SOCKS5 commands as described in RFC 1928.
const (
connect commandType = 1
bind commandType = 2
udpAssociate commandType = 3
)
// addrType are the bytes sent in SOCKS5 packets
// that represent particular address types.
type addrType byte
// The set of valid SOCKS5 address types as defined in RFC 1928.
const (
ipv4 addrType = 1
domainName addrType = 3
ipv6 addrType = 4
)
// replyCode are the bytes sent in SOCKS5 packets
// that represent replies from the server to a client
// request.
type replyCode byte
// The set of valid SOCKS5 reply types as per the RFC 1928.
const (
success replyCode = 0
generalFailure replyCode = 1
connectionNotAllowed replyCode = 2
networkUnreachable replyCode = 3
hostUnreachable replyCode = 4
connectionRefused replyCode = 5
ttlExpired replyCode = 6
commandNotSupported replyCode = 7
addrTypeNotSupported replyCode = 8
)
// UDP conn default buffer size and read timeout.
const (
bufferSize = 8 * 1024
readTimeout = 5 * time.Second
)
// Server is a SOCKS5 proxy server.
type Server struct {
// Logf optionally specifies the logger to use.
// If nil, the standard logger is used.
Logf logger.Logf
// Dialer optionally specifies the dialer to use for outgoing connections.
// If nil, the net package's standard dialer is used.
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
// Username and Password, if set, are the credential clients must provide.
Username string
Password string
}
func (s *Server) dial(ctx context.Context, network, addr string) (net.Conn, error) {
dial := s.Dialer
if dial == nil {
dialer := &net.Dialer{}
dial = dialer.DialContext
}
return dial(ctx, network, addr)
}
func (s *Server) logf(format string, args ...any) {
logf := s.Logf
if logf == nil {
logf = log.Printf
}
logf(format, args...)
}
// Serve accepts and handles incoming connections on the given listener.
func (s *Server) Serve(ln net.Listener) error {
defer ln.Close()
for {
c, err := ln.Accept()
if err != nil {
return err
}
go func() {
defer c.Close()
conn := &Conn{clientConn: c, srv: s}
err := conn.Run()
if err != nil {
s.logf("client connection failed: %v", err)
}
}()
}
}
// Conn is a SOCKS5 connection for client to reach
// server.
type Conn struct {
// The struct is filled by each of the internal
// methods in turn as the transaction progresses.
srv *Server
clientConn net.Conn
request *request
// udpClientAddr is the address the client sends its UDP datagrams from.
// The goroutine reading from the client writes it, and a goroutine per
// target reads it to address the responses, so it needs a lock.
udpClientAddr syncs.MutexValue[net.Addr]
udpTargetConns map[socksAddr]net.Conn
}
// logf logs to the server's logger, which falls back to the standard logger
// when Server.Logf is nil.
func (c *Conn) logf(format string, args ...any) {
c.srv.logf(format, args...)
}
// Run starts the new connection.
func (c *Conn) Run() error {
needAuth := c.srv.Username != "" || c.srv.Password != ""
authMethod := noAuthRequired
if needAuth {
authMethod = passwordAuth
}
err := parseClientGreeting(c.clientConn, authMethod)
if err != nil {
c.clientConn.Write([]byte{socks5Version, noAcceptableAuth})
return err
}
c.clientConn.Write([]byte{socks5Version, authMethod})
if !needAuth {
return c.handleRequest()
}
user, pwd, err := parseClientAuth(c.clientConn)
// Compare both credentials in constant time. The listener is reachable by
// any local process, so a data-dependent comparison would let one recover
// the username or password a byte at a time by timing the reject. Evaluate
// both halves unconditionally so the username result doesn't gate whether
// the password is examined.
userMatch := subtle.ConstantTimeCompare([]byte(user), []byte(c.srv.Username))
pwdMatch := subtle.ConstantTimeCompare([]byte(pwd), []byte(c.srv.Password))
if err != nil || userMatch != 1 || pwdMatch != 1 {
c.clientConn.Write([]byte{1, 1}) // auth error
return err
}
c.clientConn.Write([]byte{1, 0}) // auth success
return c.handleRequest()
}
func (c *Conn) handleRequest() error {
req, err := parseClientRequest(c.clientConn)
if err != nil {
res := errorResponse(generalFailure)
buf, _ := res.marshal()
c.clientConn.Write(buf)
return err
}
c.request = req
switch req.command {
case connect:
return c.handleTCP()
case udpAssociate:
return c.handleUDP()
default:
res := errorResponse(commandNotSupported)
buf, _ := res.marshal()
c.clientConn.Write(buf)
return fmt.Errorf("unsupported command %v", req.command)
}
}
func (c *Conn) handleTCP() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv, err := c.srv.dial(
ctx,
"tcp",
c.request.destination.hostPort(),
)
if err != nil {
res := errorResponse(generalFailure)
buf, _ := res.marshal()
c.clientConn.Write(buf)
return err
}
defer srv.Close()
localAddr := srv.LocalAddr().String()
serverAddr, serverPort, err := splitHostPort(localAddr)
if err != nil {
return err
}
res := &response{
reply: success,
bindAddr: socksAddr{
addrType: getAddrType(serverAddr),
addr: serverAddr,
port: serverPort,
},
}
buf, err := res.marshal()
if err != nil {
res = errorResponse(generalFailure)
buf, _ = res.marshal()
}
c.clientConn.Write(buf)
errc := make(chan error, 2)
go func() { errc <- pump(c.clientConn, srv, "from backend to client") }()
go func() { errc <- pump(srv, c.clientConn, "from client to backend") }()
err = <-errc
if err == nil {
return <-errc
}
// One direction failed, which means one of the two sockets is dead.
//
// If it failed writing to (or half-closing) its destination, the other
// direction is reading from that dead socket. Its reads return whatever
// arrived before the failure and then an error, so leave it alone to
// deliver that data rather than truncating it.
//
// If it failed reading from its source, the other direction is writing
// into that dead socket and may be blocked reading from the live peer,
// which has no reason to send anything. Close both connections to
// unblock it. Nothing deliverable is lost: the dead socket has already
// returned everything it received, and anything from the live peer
// could not be delivered to it anyway.
if _, isDstErr := errors.AsType[dstError](err); !isDstErr {
srv.Close()
c.clientConn.Close()
}
<-errc
return err
}
// pump copies from src to dst until src returns EOF or either side fails.
//
// On EOF it half-closes dst, if dst supports it, so dst's peer sees EOF while
// the other direction of the proxied connection keeps flowing. It does not
// half-close src's read side: that has no effect on the wire, and after src
// has returned EOF, both macOS and Linux may reject the shutdown with
// ENOTCONN.
//
// Errors from writing to or half-closing dst are returned wrapped in a
// [dstError]. Errors from reading src are not.
//
// As of 2026-09-16, both ends of a proxied connection support half-closing:
// [Server.dial] returns a [*net.TCPConn], possibly wrapped by a
// tsdial.sysConn, and the client connection comes from a TCP listener,
// possibly wrapped by proxymux.SplitSOCKSAndHTTP. Both wrappers pass
// half-closes through to the underlying connection.
func pump(dst, src net.Conn, dir string) error {
if _, err := io.Copy(dstWriter{dst}, src); err != nil {
return fmt.Errorf("%s: %w", dir, err)
}
if hc, ok := dst.(nettype.HalfCloser); ok {
if err := hc.CloseWrite(); err != nil {
return fmt.Errorf("%s: close write: %w", dir, dstError{err})
}
}
return nil
}
// dstError wraps an error from a [pump]'s destination, as opposed to one
// from reading its source.
type dstError struct{ error }
func (e dstError) Unwrap() error { return e.error }
// dstWriter wraps a [pump]'s destination so that its write errors are
// reported as a [dstError].
//
// Wrapping the destination hides its ReadFrom method from [io.Copy] and hides
// its type from the source's WriteTo, so when both ends are bare TCP
// connections the kernel splice fast path is not used. That costs about a
// third of the relay throughput on Linux loopback, but knowing which side
// failed is what lets handleTCP avoid truncating a reply after the backend
// resets, and in the common userspace-networking and proxymux configurations
// the connections are wrapped types with no fast path anyway.
type dstWriter struct{ io.Writer }
func (w dstWriter) Write(p []byte) (int, error) {
n, err := w.Writer.Write(p)
if err != nil {
err = dstError{err}
}
return n, err
}
func (c *Conn) handleUDP() error {
// The DST.ADDR and DST.PORT fields contain the address and port that
// the client expects to use to send UDP datagrams on for the
// association. The server MAY use this information to limit access
// to the association.
// @see Page 6, https://datatracker.ietf.org/doc/html/rfc1928.
//
// We do NOT limit the access from the client currently in this implementation.
_ = c.request.destination
addr := c.clientConn.LocalAddr()
host, _, err := net.SplitHostPort(addr.String())
if err != nil {
return err
}
clientUDPConn, err := net.ListenPacket("udp", net.JoinHostPort(host, "0"))
if err != nil {
res := errorResponse(generalFailure)
buf, _ := res.marshal()
c.clientConn.Write(buf)
return err
}
defer clientUDPConn.Close()
bindAddr, bindPort, err := splitHostPort(clientUDPConn.LocalAddr().String())
if err != nil {
return err
}
res := &response{
reply: success,
bindAddr: socksAddr{
addrType: getAddrType(bindAddr),
addr: bindAddr,
port: bindPort,
},
}
buf, err := res.marshal()
if err != nil {
res = errorResponse(generalFailure)
buf, _ = res.marshal()
}
c.clientConn.Write(buf)
return c.transferUDP(c.clientConn, clientUDPConn)
}
func (c *Conn) transferUDP(associatedTCP net.Conn, clientConn net.PacketConn) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// client -> target
go func() {
defer cancel()
c.udpTargetConns = make(map[socksAddr]net.Conn)
// close all target udp connections when the client connection is closed
defer func() {
for _, conn := range c.udpTargetConns {
_ = conn.Close()
}
}()
buf := make([]byte, bufferSize)
for {
select {
case <-ctx.Done():
return
default:
err := c.handleUDPRequest(ctx, clientConn, buf)
if err != nil {
if isTimeout(err) {
continue
}
if errors.Is(err, net.ErrClosed) {
return
}
c.logf("udp transfer: handle udp request fail: %v", err)
}
}
}
}()
// A UDP association terminates when the TCP connection that the UDP
// ASSOCIATE request arrived on terminates. RFC1928
_, err := io.Copy(io.Discard, associatedTCP)
if err != nil {
err = fmt.Errorf("udp associated tcp conn: %w", err)
}
return err
}
func (c *Conn) getOrDialTargetConn(
ctx context.Context,
clientConn net.PacketConn,
targetAddr socksAddr,
) (net.Conn, error) {
conn, exist := c.udpTargetConns[targetAddr]
if exist {
return conn, nil
}
conn, err := c.srv.dial(ctx, "udp", targetAddr.hostPort())
if err != nil {
return nil, err
}
c.udpTargetConns[targetAddr] = conn
// target -> client
go func() {
buf := make([]byte, bufferSize)
for {
select {
case <-ctx.Done():
return
default:
err := c.handleUDPResponse(clientConn, targetAddr, conn, buf)
if err != nil {
if isTimeout(err) {
continue
}
if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) {
return
}
c.logf("udp transfer: handle udp response fail: %v", err)
}
}
}
}()
return conn, nil
}
func (c *Conn) handleUDPRequest(
ctx context.Context,
clientConn net.PacketConn,
buf []byte,
) error {
// add a deadline for the read to avoid blocking forever
_ = clientConn.SetReadDeadline(time.Now().Add(readTimeout))
n, addr, err := clientConn.ReadFrom(buf)
if err != nil {
return fmt.Errorf("read from client: %w", err)
}
c.udpClientAddr.Store(addr)
req, data, err := parseUDPRequest(buf[:n])
if err != nil {
return fmt.Errorf("parse udp request: %w", err)
}
targetConn, err := c.getOrDialTargetConn(ctx, clientConn, req.addr)
if err != nil {
return fmt.Errorf("dial target %s fail: %w", req.addr, err)
}
nn, err := targetConn.Write(data)
if err != nil {
return fmt.Errorf("write to target %s fail: %w", req.addr, err)
}
if nn != len(data) {
return fmt.Errorf("write to target %s fail: %w", req.addr, io.ErrShortWrite)
}
return nil
}
func (c *Conn) handleUDPResponse(
clientConn net.PacketConn,
targetAddr socksAddr,
targetConn net.Conn,
buf []byte,
) error {
// add a deadline for the read to avoid blocking forever
_ = targetConn.SetReadDeadline(time.Now().Add(readTimeout))
n, err := targetConn.Read(buf)
if err != nil {
return fmt.Errorf("read from target: %w", err)
}
hdr := udpRequest{addr: targetAddr}
pkt, err := hdr.marshal()
if err != nil {
return fmt.Errorf("marshal udp request: %w", err)
}
data := append(pkt, buf[:n]...)
// use addr from client to send back
nn, err := clientConn.WriteTo(data, c.udpClientAddr.Load())
if err != nil {
return fmt.Errorf("write to client: %w", err)
}
if nn != len(data) {
return fmt.Errorf("write to client: %w", io.ErrShortWrite)
}
return nil
}
func isTimeout(err error) bool {
terr, ok := errors.Unwrap(err).(interface{ Timeout() bool })
return ok && terr.Timeout()
}
func splitHostPort(hostport string) (host string, port uint16, err error) {
host, portStr, err := net.SplitHostPort(hostport)
if err != nil {
return "", 0, err
}
portInt, err := strconv.Atoi(portStr)
if err != nil {
return "", 0, err
}
if portInt < 0 || portInt > 65535 {
return "", 0, fmt.Errorf("invalid port number %d", portInt)
}
return host, uint16(portInt), nil
}
// parseClientGreeting parses a request initiation packet.
func parseClientGreeting(r io.Reader, authMethod byte) error {
var hdr [2]byte
_, err := io.ReadFull(r, hdr[:])
if err != nil {
return fmt.Errorf("could not read packet header")
}
if hdr[0] != socks5Version {
return fmt.Errorf("incompatible SOCKS version")
}
count := int(hdr[1])
methods := make([]byte, count)
_, err = io.ReadFull(r, methods)
if err != nil {
return fmt.Errorf("could not read methods")
}
if slices.Contains(methods, authMethod) {
return nil
}
return fmt.Errorf("no acceptable auth methods")
}
func parseClientAuth(r io.Reader) (usr, pwd string, err error) {
var hdr [2]byte
if _, err := io.ReadFull(r, hdr[:]); err != nil {
return "", "", fmt.Errorf("could not read auth packet header")
}
if hdr[0] != passwordAuthVersion {
return "", "", fmt.Errorf("bad SOCKS auth version")
}
usrLen := int(hdr[1])
usrBytes := make([]byte, usrLen)
if _, err := io.ReadFull(r, usrBytes); err != nil {
return "", "", fmt.Errorf("could not read auth packet username")
}
var hdrPwd [1]byte
if _, err := io.ReadFull(r, hdrPwd[:]); err != nil {
return "", "", fmt.Errorf("could not read auth packet password length")
}
pwdLen := int(hdrPwd[0])
pwdBytes := make([]byte, pwdLen)
if _, err := io.ReadFull(r, pwdBytes); err != nil {
return "", "", fmt.Errorf("could not read auth packet password")
}
return string(usrBytes), string(pwdBytes), nil
}
func getAddrType(addr string) addrType {
if ip := net.ParseIP(addr); ip != nil {
if ip.To4() != nil {
return ipv4
}
return ipv6
}
return domainName
}
// request represents data contained within a SOCKS5
// connection request packet.
type request struct {
command commandType
destination socksAddr
}
// parseClientRequest converts raw packet bytes into a
// SOCKS5Request struct.
func parseClientRequest(r io.Reader) (*request, error) {
var hdr [3]byte
_, err := io.ReadFull(r, hdr[:])
if err != nil {
return nil, fmt.Errorf("could not read packet header")
}
cmd := hdr[1]
destination, err := parseSocksAddr(r)
return &request{
command: commandType(cmd),
destination: destination,
}, err
}
type socksAddr struct {
addrType addrType
addr string
port uint16
}
var zeroSocksAddr = socksAddr{addrType: ipv4, addr: "0.0.0.0", port: 0}
func parseSocksAddr(r io.Reader) (addr socksAddr, err error) {
var addrTypeData [1]byte
_, err = io.ReadFull(r, addrTypeData[:])
if err != nil {
return socksAddr{}, fmt.Errorf("could not read address type")
}
dstAddrType := addrType(addrTypeData[0])
var destination string
switch dstAddrType {
case ipv4:
var ip [4]byte
_, err = io.ReadFull(r, ip[:])
if err != nil {
return socksAddr{}, fmt.Errorf("could not read IPv4 address")
}
destination = net.IP(ip[:]).String()
case domainName:
var dstSizeByte [1]byte
_, err = io.ReadFull(r, dstSizeByte[:])
if err != nil {
return socksAddr{}, fmt.Errorf("could not read domain name size")
}
dstSize := int(dstSizeByte[0])
domainName := make([]byte, dstSize)
_, err = io.ReadFull(r, domainName)
if err != nil {
return socksAddr{}, fmt.Errorf("could not read domain name")
}
destination = string(domainName)
case ipv6:
var ip [16]byte
_, err = io.ReadFull(r, ip[:])
if err != nil {
return socksAddr{}, fmt.Errorf("could not read IPv6 address")
}
destination = net.IP(ip[:]).String()
default:
return socksAddr{}, fmt.Errorf("unsupported address type")
}
var portBytes [2]byte
_, err = io.ReadFull(r, portBytes[:])
if err != nil {
return socksAddr{}, fmt.Errorf("could not read port")
}
port := binary.BigEndian.Uint16(portBytes[:])
return socksAddr{
addrType: dstAddrType,
addr: destination,
port: port,
}, nil
}
func (s socksAddr) marshal() ([]byte, error) {
var addr []byte
switch s.addrType {
case ipv4:
addr = net.ParseIP(s.addr).To4()
if addr == nil {
return nil, fmt.Errorf("invalid IPv4 address for binding")
}
case domainName:
if len(s.addr) > 255 {
return nil, fmt.Errorf("invalid domain name for binding")
}
addr = make([]byte, 0, len(s.addr)+1)
addr = append(addr, byte(len(s.addr)))
addr = append(addr, []byte(s.addr)...)
case ipv6:
addr = net.ParseIP(s.addr).To16()
if addr == nil {
return nil, fmt.Errorf("invalid IPv6 address for binding")
}
default:
return nil, fmt.Errorf("unsupported address type")
}
pkt := []byte{byte(s.addrType)}
pkt = append(pkt, addr...)
pkt = binary.BigEndian.AppendUint16(pkt, s.port)
return pkt, nil
}
func (s socksAddr) hostPort() string {
return net.JoinHostPort(s.addr, strconv.Itoa(int(s.port)))
}
func (s socksAddr) String() string {
return s.hostPort()
}
// response contains the contents of
// a response packet sent from the proxy
// to the client.
type response struct {
reply replyCode
bindAddr socksAddr
}
func errorResponse(code replyCode) *response {
return &response{reply: code, bindAddr: zeroSocksAddr}
}
// marshal converts a SOCKS5Response struct into
// a packet. If res.reply == Success, it may throw an error on
// receiving an invalid bind address. Otherwise, it will not throw.
func (res *response) marshal() ([]byte, error) {
pkt := make([]byte, 3)
pkt[0] = socks5Version
pkt[1] = byte(res.reply)
pkt[2] = 0 // null reserved byte
addrPkt, err := res.bindAddr.marshal()
if err != nil {
return nil, err
}
return append(pkt, addrPkt...), nil
}
type udpRequest struct {
frag byte
addr socksAddr
}
// +----+------+------+----------+----------+----------+
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
// +----+------+------+----------+----------+----------+
// | 2 | 1 | 1 | Variable | 2 | Variable |
// +----+------+------+----------+----------+----------+
func parseUDPRequest(data []byte) (*udpRequest, []byte, error) {
if len(data) < 4 {
return nil, nil, fmt.Errorf("invalid packet length")
}
// reserved bytes
if !(data[0] == 0 && data[1] == 0) {
return nil, nil, fmt.Errorf("invalid udp request header")
}
frag := data[2]
reader := bytes.NewReader(data[3:])
addr, err := parseSocksAddr(reader)
bodyLen := reader.Len() // (*bytes.Reader).Len() return unread data length
body := data[len(data)-bodyLen:]
return &udpRequest{
frag: frag,
addr: addr,
}, body, err
}
func (u *udpRequest) marshal() ([]byte, error) {
pkt := make([]byte, 3)
pkt[0] = 0
pkt[1] = 0
pkt[2] = u.frag
addrPkt, err := u.addr.marshal()
if err != nil {
return nil, err
}
return append(pkt, addrPkt...), nil
}