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
- 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
- Server side: implement bind by adding a case bind: return c.handleBind() arm to the switch
- Log req.command at connect time to identify the offending client
- 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
- Use CONNECT for TCP and UDP ASSOCIATE for UDP; no mainstream use case needs BIND (FTP active mode should switch to passive)
- Validate the command byte after parsing and reject/log before dispatch
- Expect the 0x07 (command not supported) reply and surface it as a client-side configuration error
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
- parse udp request: %w
- from backend to client: %w
- from client to backend: %w
- udp associated tcp conn: %w
- read from client: %w
AI-assisted analysis of tailscale/tailscale@57c3357fdb (2026-08-18).
Data as JSON: /api/errors/47bd9c146ca81fbf.
Report an issue: GitHub.