ginuerzh/gost · warning

bad type

Error message

bad type

What it means

ErrBadType is a sentinel from the TLS obfs (otls) dissector, returned during Parse when a byte does not match the expected TLS record type for the current state, and during serverHandshake when the parsed TLS record's Type is not Handshake. It means the data stream does not look like valid TLS traffic.

Source

Thrown at obfs.go:320

var (
	cipherSuites = []uint16{
		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. Ensure both endpoints use the same obfs method (otls) and parameters.
  2. Check the very first bytes the client sends are a TLS ClientHello (record type 0x16).
  3. If you see this during legit traffic, verify no intermediary proxy is mangling/padding the stream.
  4. Match library versions on client and server; record-state expectations differ across versions.

Example fix

// server config before (expects TLS-looking traffic)
dialer: {type: "obfs", obfs: "ohttp"}
// after (client sends TLS records)
dialer: {type: "obfs", obfs: "otls"}
Defensive patterns

Strategy: validation

Validate before calling

// preflight: ensure first byte of a client-side stream is a TLS record type
if len(buf) > 0 {
	switch buf[0] {
	case 0x16, 0x14, 0x17:
		// plausible TLS record
	default:
		return errors.New("stream does not start with a TLS record; check obfs=otls on both ends")
	}
}

Type guard

func isTLSRecordType(b byte) bool {
	for _, t := range []byte{0x16, 0x14, 0x17} {
		if b == t { return true }
	}
	return false
}

Try / catch

n, err := dissector.Parse(conn, buf)
if errors.Is(err, dissector.ErrBadType) {
	log.Printf("non-TLS traffic on otls port from %s; closing", conn.RemoteAddr())
	conn.Close()
}

Prevention

When it happens

Trigger: Parse (obfs.go:362): a record's first byte differs from the expected tlsRecordTypes entry for the current step (0x16, 0x14, 0x16, 0x17). serverHandshake (obfs.go:533): record.Type != dissector.Handshake — the client sent a non-handshake TLS record first.

Common situations: Non-TLS clients connecting to an otls-obfuscated server (scanners, wrong transport); client/server obfs mismatch (ohttp server vs otls client); truncated or corrupted streams desynchronizing the record parser; TLS library version differences sending unexpected initial records.

Related errors


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