ginuerzh/gost · warning

bad minor version

Error message

bad minor version

What it means

ErrBadMinorVersion is returned by the TLS obfs dissector's Parse when the record's minor version byte does not match the expected value (0x01 for the initial ClientHello record, 0x03 afterwards, per tlsVersionMinors). It indicates the stream is not conformant TLS traffic for the expected record sequence.

Source

Thrown at obfs.go:322

		0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8, 0xccaa, 0xc02b, 0xc02f,
		0x009e, 0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067, 0xc00a,
		0xc014, 0x0039, 0xc009, 0xc013, 0x0033, 0x009d, 0x009c, 0x003d,
		0x003c, 0x0035, 0x002f, 0x00ff,
	}

	compressionMethods = []uint8{0x00}

	algorithms = []uint16{
		0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402,
		0x0403, 0x0301, 0x0302, 0x0303, 0x0201, 0x0202, 0x0203,
	}

	tlsRecordTypes   = []uint8{0x16, 0x14, 0x16, 0x17}
	tlsVersionMinors = []uint8{0x01, 0x03, 0x03, 0x03}

	ErrBadType         = errors.New("bad type")
	ErrBadMajorVersion = errors.New("bad major version")
	ErrBadMinorVersion = errors.New("bad minor version")
	ErrMaxDataLen      = errors.New("bad tls data len")
)

const (
	tlsRecordStateType = iota
	tlsRecordStateVersion0
	tlsRecordStateVersion1
	tlsRecordStateLength0
	tlsRecordStateLength1
	tlsRecordStateData
)

type obfsTLSParser struct {
	step   uint8
	state  uint8
	length uint16
}

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Verify both endpoints use matching otls obfs settings and library versions.
  2. Check for middleboxes/ISPs rewriting TLS record version bytes on the path.
  3. Close and re-establish the connection; the dissector state is unrecoverable after this error.
  4. If caused by scanners, restrict exposure (firewall rules, port knocking, or fallback plain-HTTP camouflage).
Defensive patterns

Strategy: validation

Validate before calling

// check record minor version byte against expected sequence [0x01,0x03,0x03,0x03]
expected := []byte{0x01, 0x03, 0x03, 0x03}
if int(step) < len(expected) && buf[2] != expected[step] {
	return errors.New("unexpected TLS minor version in record header")
}

Type guard

func minorVersionOK(hdr []byte, step int) bool {
	m := []byte{0x01, 0x03, 0x03, 0x03}
	return len(hdr) >= 3 && step < len(m) && hdr[2] == m[step]
}

Try / catch

n, err := dissector.Parse(conn, buf)
if errors.Is(err, dissector.ErrBadMinorVersion) {
	conn.Close()
	return fmt.Errorf("bad TLS minor version from %s; possible middlebox or scanner", conn.RemoteAddr())
}

Prevention

When it happens

Trigger: During Parse at obfs.go:374, in state tlsRecordStateVersion1, the minor-version byte differs from tlsVersionMinors[r.step]; e.g. a ClientHello claiming an unexpected minor version, or garbage/scanner bytes landing in that position.

Common situations: Probes/scanners sending random bytes to the otls port; TLS implementations with unusual version negotiation (some middleboxes rewrite version bytes); obfs method mismatch; desynchronized streams from earlier corrupted records.

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/9f83105d755d30ea. Report an issue: GitHub.