tailscale/tailscale · error

unsupported command %v

Error message

unsupported command %v

What it means

The SOCKS5 server only implements CONNECT (1) and UDP ASSOCIATE (3). A client request with any other command byte — practically BIND (2), since those are the three RFC 1928 commands — gets a commandNotSupported (0x07) reply written to the client and then this error. It is a protocol-level rejection, not an I/O failure.

Source

Thrown at net/socks5/socks5.go:212

	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()

View on GitHub (pinned to 57c3357fdb)

Solutions

  1. Client side: use CONNECT for TCP and UDP ASSOCIATE for UDP; for FTP switch the client to passive (PASV) mode so no BIND is needed
  2. Server side: implement bind by adding a case bind: return c.handleBind() arm to the switch
  3. Log req.command at connect time to identify the offending client
  4. If garbage commands appear, check for a non-SOCKS5 client (e.g. plain HTTP proxy request) hitting the SOCKS port

Example fix

// before
switch req.command {
case connect:
    return c.handleTCP()
case udpAssociate:
    return c.handleUDP()
default:
    return fmt.Errorf("unsupported command %v", req.command)
}

// after: support BIND for legacy FTP
case bind:
    return c.handleBind()
Defensive patterns

Strategy: validation

Validate before calling

// client side: only ever send supported commands
const (
    cmdConnect      byte = 1
    cmdUDPAssociate byte = 3
)
req := []byte{5, cmdConnect, 0, 1, ...} // never 2 (BIND)

Type guard

func supportedCommand(cmd byte) bool { return cmd == 1 || cmd == 3 }

Prevention

When it happens

Trigger: Conn.Run parses a request whose req.command is not connect (1) or udpAssociate (3) — e.g. byte 2 (bind) or 0/garbage — hits the default switch arm at socks5.go:204, writes errorResponse(commandNotSupported), and returns fmt.Errorf("unsupported command %v", req.command).

Common situations: FTP clients in active mode (they issue BIND); some P2P/game clients requiring BIND; hand-rolled clients sending a zeroed command byte; version drift where a client sends an extended command the server doesn't know.

Related errors


AI-assisted analysis of tailscale/tailscale@57c3357fdb (2026-08-18). Data as JSON: /api/errors/47bd9c146ca81fbf. Report an issue: GitHub.