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
- Verify both endpoints use matching otls obfs settings and library versions.
- Check for middleboxes/ISPs rewriting TLS record version bytes on the path.
- Close and re-establish the connection; the dissector state is unrecoverable after this error.
- 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
- Test the full network path for TLS-mutating middleboxes when this error appears on legit traffic.
- Use matching library versions so expected version sequences align.
- Close and reconnect on this error; never attempt to resync the dissector.
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.