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
- Point only SOCKS5-capable clients at the proxy port; move health checks to a dedicated HTTP endpoint.
- Set a read deadline on the conn before ReadFull so stalled clients time out cleanly instead of hanging.
- Log the bytes received (if any) to detect non-SOCKS protocols probing the port.
- 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
- Point only SOCKS5 clients at the proxy port
- Send the greeting as one atomic write
- Set read deadlines on every handshake
- Keep health checks on a separate HTTP port
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.
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- %s [socks5_unsupported_version]
- short oracle data packet
- oracle resend is not supported
- %s: %w [command_read_failed]
- %s: %w [listen_port_failed]
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/fc8f9d3e2e3dd3d0.
Report an issue: GitHub.