shadow1ng/fscan · error

%s: %w [socks5_handshake_read_failed]

Error message

%s: %w [socks5_handshake_read_failed]

What it means

Wrap of an io.ReadFull failure at the start of the SOCKS5 handshake: fewer than the 2 header bytes (VER + NMETHODS) arrived from the client, usually because the client disconnected, sent a non-SOCKS5 protocol, or the connection timed out. The i18n socks5_handshake_read_failed prefix identifies the handshake read stage; %w preserves the I/O cause.

Source

Thrown at plugins/local/socks5proxy.go:165

	if err != nil {
		if ctx.Err() == nil {
			session.LogError(i18n.Tr("socks5_request_failed", err))
		}
		return
	}
	defer func() { _ = targetConn.Close() }()

	session.LogSuccess(i18n.GetText("socks5_connected"))

	// 双向数据转发
	p.relayData(clientConn, targetConn)
}

// handleSocks5Handshake 处理SOCKS5握手
func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error {
	header := make([]byte, 2)
	if _, err := io.ReadFull(conn, header); err != nil {
		return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err)
	}

	if header[0] != 0x05 || header[1] == 0 {
		return fmt.Errorf("%s", i18n.GetText("socks5_unsupported_version"))
	}
	methods := make([]byte, int(header[1]))
	if _, err := io.ReadFull(conn, methods); err != nil {
		return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err)
	}
	if !containsByte(methods, 0x00) {
		_, _ = conn.Write([]byte{0x05, 0xff})
		return fmt.Errorf("%s", i18n.GetText("socks5_unsupported_version"))
	}

	// 发送握手响应(无认证)
	response := []byte{0x05, 0x00} // 版本5,无认证
	if _, err := conn.Write(response); err != nil {
		return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_write_failed"), err)

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Point only SOCKS5-capable clients at the proxy port; move health checks to a dedicated HTTP endpoint.
  2. Set a read deadline on the conn before ReadFull so stalled clients time out cleanly instead of hanging.
  3. Log the bytes received (if any) to detect non-SOCKS protocols probing the port.
  4. Retry the connection from the client; if persistent, check for middleboxes resetting the TCP stream.

Example fix

// before
header := make([]byte, 2)
if _, err := io.ReadFull(conn, header); err != nil {
	return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err)
}
// after — bound the handshake so dead clients don't hang the handler
if err := conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil {
	return err
}
header := make([]byte, 2)
if _, err := io.ReadFull(conn, header); err != nil {
	if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
		return fmt.Errorf("client closed before SOCKS5 greeting")
	}
	return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err)
}
Defensive patterns

Strategy: validation

Validate before calling

// caller-side sanity before connecting
func probeSocks5(addr string, timeout time.Duration) error {
	c, err := net.DialTimeout("tcp", addr, timeout)
	if err != nil {
		return err
	}
	defer c.Close()
	c.SetDeadline(time.Now().Add(timeout))
	_, err = c.Write([]byte{0x05, 0x01, 0x00})
	return err
}

Type guard

func isShortRead(err error) bool {
	return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
}

Try / catch

if err := handleSocks5Handshake(conn); err != nil {
	if isShortRead(errors.Unwrap(err)) {
		// client vanished mid-handshake: close quietly, no alert
		return nil
	}
	return err
}

Prevention

When it happens

Trigger: io.ReadFull(conn, header[:2]) in handleSocks5Handshake fails — client closed before sending 2 bytes, sent a partial greeting, or a non-SOCKS client (e.g. an HTTP health check) connected to the port.

Common situations: Port scanners or browsers pointing plain HTTP at the SOCKS port; load balancer health probes connecting and disconnecting; client using SOCKS4, which sends a different greeting; network reset mid-handshake.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/fc8f9d3e2e3dd3d0. Report an issue: GitHub.