XTLS/Xray-core · error

read padding turn %d: %w

Error message

read padding turn %d: %w

What it means

runPaddingSchedule failed while reading the peer's padding turn i: the record length header could not be read, the length was implausible (<header size or >8 MiB), the total fell outside the turn's accepted range/variant set, or the body read hit EOF. In practice this means the peer's padding does not match what this side expects.

Source

Thrown at transport/internet/finalmask/xmc/padding.go:67

		return err
	}

	var writeBuffer []byte
	for i, turn := range schedule {
		prefixLength := 0
		if i == 0 {
			prefixLength = firstTurnPrefixLength
		}

		localSends := isClient == (turn.direction == paddingClientToServer)
		if localSends {
			if err := writePaddingTurnWithBuffer(writer, turn, prefixLength, time.Sleep, &writeBuffer); err != nil {
				return fmt.Errorf("write padding turn %d: %w", i, err)
			}
			continue
		}
		if err := readPaddingTurn(reader, turn, prefixLength); err != nil {
			return fmt.Errorf("read padding turn %d: %w", i, err)
		}
	}
	return nil
}

func validatePaddingSchedule(schedule []paddingTurn, firstTurnPrefixLength int) error {
	if len(schedule) == 0 {
		return fmt.Errorf("empty padding schedule")
	}
	if firstTurnPrefixLength < 0 {
		return fmt.Errorf("negative first turn prefix length: %d", firstTurnPrefixLength)
	}
	if firstTurnPrefixLength > 0 && schedule[0].direction != paddingClientToServer {
		return fmt.Errorf("first prefixed padding turn is not client-to-server")
	}

	for i, turn := range schedule {
		if turn.direction != paddingClientToServer && turn.direction != paddingServerToClient {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Confirm both ends run the same xmc version so padding schedules match
  2. Verify the remote address is the xmc counterpart, not a vanilla Minecraft or HTTP server
  3. Check for full-stream connectivity (the wrapped error often is a read timeout or EOF)
  4. Treat as fatal: close the connection and retry the dial
Defensive patterns

Strategy: try-catch

Try / catch

err := runPaddingSchedule(...)
if err != nil && strings.Contains(err.Error(), "read padding turn") {
    // peer padding mismatch: wrong version or wrong endpoint; abort dial
    return err
}

Prevention

When it happens

Trigger: Client and server padding schedules differ (version mismatch); peer is not an xmc endpoint so its bytes are not padding records; connection drops mid-padding so readPaddingTurn gets EOF/partial data.

Common situations: Mixing library versions where newClientPaddingSchedule2612 changed; pointing the transport at a real Minecraft server or random service; middlebox truncating the handshake; test harnesses feeding arbitrary bytes.

Related errors


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