XTLS/Xray-core · error

failed to read domain for socks 4a

Error message

failed to read domain for socks 4a

What it means

Thrown in handshake4 (proxy/socks/protocol.go:78) for a SOCKS4a request. When the 4-byte address starts with 0x00 the client is expected to send a null-terminated domain name after the userid; ReadUntilNull failing (EOF/reset before the terminating 0x00) produces this error.

Source

Thrown at proxy/socks/protocol.go:78

	{
		buffer := buf.StackNew()
		if _, err := buffer.ReadFullFrom(reader, 6); err != nil {
			buffer.Release()
			return nil, errors.New("insufficient header").Base(err)
		}
		port = net.PortFromBytes(buffer.BytesRange(0, 2))
		address = net.IPAddress(buffer.BytesRange(2, 6))
		buffer.Release()
	}

	if _, err := ReadUntilNull(reader); /* user id */ err != nil {
		return nil, err
	}
	if address.IP()[0] == 0x00 {
		domain, err := ReadUntilNull(reader)
		if err != nil {
			return nil, errors.New("failed to read domain for socks 4a").Base(err)
		}
		address = net.ParseAddress(domain)
	}

	switch cmd {
	case cmdTCPConnect:
		request := &protocol.RequestHeader{
			Command: protocol.RequestCommandTCP,
			Address: address,
			Port:    port,
			Version: socks4Version,
		}
		if err := writeSocks4Response(writer, socks4RequestGranted, net.AnyIP, net.Port(0)); err != nil {
			return nil, err
		}
		return request, nil
	default:
		writeSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0))

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Fix the client to send: USERID + 0x00 + DOMAIN + 0x00 after the 6-byte header, with DSTIP = 0x00 0x00 0x00 <non-zero>.
  2. Prefer SOCKS4a's modern replacement: use SOCKS5 hostname support (cmdTorResolve/connect with address type domain) instead.
  3. Check the base error for EOF/reset to distinguish a client bug from a network break.

Example fix

// before: 4a marker set but no domain sent
conn.Write([]byte{0x04, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0xFF, 0x00}) // then close

// after: append null-terminated domain
conn.Write([]byte{0x04, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0xFF, 0x00})
conn.Write([]byte("example.com\x00"))
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: build a well-formed SOCKS4a request with null-terminated fields
func socks4aRequest(w io.Writer, domain string, port uint16) error {
    if len(domain) > 253 || strings.IndexByte(domain, 0) >= 0 {
        return fmt.Errorf("invalid domain %q", domain)
    }
    req := []byte{0x04, 0x01, byte(port >> 8), byte(port), 0, 0, 0, 0xFF, 0x00}
    req = append(req, domain...)
    req = append(req, 0x00)
    _, err := w.Write(req)
    return err
}

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to read domain for socks 4a") {
    return errors.New("client sent a SOCKS4a request without a null-terminated domain")
}

Prevention

When it happens

Trigger: Client sets DSTIP bytes 0-2 to non-zero and byte 3 to 0x00 (the 4a convention) but never sends the domain string, sends it without the trailing null byte, or the connection breaks before the domain arrives.

Common situations: Buggy SOCKS4a client implementations that forget the null terminator; clients that craft the 4a marker bytes incorrectly; connection drops during slow handshakes.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/a961b897d0bdcfb9. Report an issue: GitHub.