151 lines
2.9 KiB
Go
151 lines
2.9 KiB
Go
package protocol
|
|
|
|
import (
|
|
"log"
|
|
|
|
"golang.org/x/crypto/cryptobyte"
|
|
)
|
|
|
|
func init() {
|
|
Register(Both, "\x10?*MQTT", detectMQTT)
|
|
}
|
|
|
|
func detectMQTT(dir Direction, data []byte, srcPort, dstPort int) (proto *Protocol, confidence float64) {
|
|
stream := cryptobyte.String(data)
|
|
|
|
var packetType byte
|
|
if !stream.ReadUint8(&packetType) || packetType != 0x10 {
|
|
// Not an MQTT CONNECT control packet.
|
|
return
|
|
}
|
|
|
|
// We read the value but only check in the end if we have bytes remaining.
|
|
var remainingLength uint32
|
|
if !readMQTTVariableLengthUint32(&stream, &remainingLength) {
|
|
return
|
|
}
|
|
|
|
var protocolName cryptobyte.String
|
|
if !stream.ReadUint16LengthPrefixed(&protocolName) || string(protocolName) != "MQTT" {
|
|
return
|
|
}
|
|
|
|
// We are reasonabily sure this is MQTT now.
|
|
proto = &Protocol{
|
|
Name: ProtocolMQTT,
|
|
}
|
|
confidence = 0.5
|
|
|
|
var protocolVersion uint8
|
|
if !stream.ReadUint8(&protocolVersion) {
|
|
return
|
|
}
|
|
switch protocolVersion {
|
|
case 4:
|
|
proto.Version = Version{Major: 3, Minor: 1, Patch: 1}
|
|
confidence += .2
|
|
case 5:
|
|
proto.Version = Version{Major: 5, Minor: 0, Patch: -1}
|
|
confidence += .2
|
|
}
|
|
|
|
var connectFlags uint8
|
|
if !stream.ReadUint8(&connectFlags) { // read connect flags
|
|
return
|
|
}
|
|
confidence += .05
|
|
var (
|
|
hasUsername = (connectFlags&0b10000000)>>7 == 1
|
|
hasPassword = (connectFlags&0b01000000)>>6 == 1
|
|
willFlag = (connectFlags&0b00000100)>>2 == 1
|
|
reserved = (connectFlags&0b00000001)>>0 == 1
|
|
discard cryptobyte.String
|
|
)
|
|
if reserved {
|
|
// Reserved bit is supposed to be not set.
|
|
confidence -= .2
|
|
}
|
|
|
|
var keepAlive uint16
|
|
if !stream.ReadUint16(&keepAlive) {
|
|
return
|
|
}
|
|
|
|
if !stream.ReadUint16LengthPrefixed(&discard) { // read client ID
|
|
return
|
|
}
|
|
confidence += .05
|
|
discard = discard[:0]
|
|
|
|
if willFlag {
|
|
if !stream.ReadUint16LengthPrefixed(&discard) { // read will topic
|
|
log.Println("will topic fail")
|
|
return
|
|
}
|
|
discard = discard[:0]
|
|
if !stream.ReadUint16LengthPrefixed(&discard) { // read will message
|
|
log.Println("will message fail")
|
|
return
|
|
}
|
|
discard = discard[:0]
|
|
confidence += .05
|
|
} else {
|
|
confidence += .05
|
|
}
|
|
|
|
if hasUsername {
|
|
if !stream.ReadUint16LengthPrefixed(&discard) { // read username
|
|
log.Println("user fail")
|
|
return
|
|
}
|
|
discard = discard[:0]
|
|
confidence += .05
|
|
} else {
|
|
confidence += .05
|
|
}
|
|
|
|
if hasPassword {
|
|
if !stream.ReadUint16LengthPrefixed(&discard) { // read password
|
|
log.Println("pass fail")
|
|
return
|
|
}
|
|
confidence += .05
|
|
} else {
|
|
confidence += .05
|
|
}
|
|
|
|
if !stream.Empty() {
|
|
confidence -= .2
|
|
} else {
|
|
confidence += .04
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func readMQTTVariableLengthUint32(stream *cryptobyte.String, value *uint32) bool {
|
|
var (
|
|
multiplier uint32 = 1
|
|
read int
|
|
)
|
|
for {
|
|
var encodedByte byte
|
|
if !stream.ReadUint8(&encodedByte) {
|
|
return false
|
|
}
|
|
read++
|
|
|
|
*value += uint32(encodedByte&0x7F) * multiplier
|
|
if encodedByte&0x80 == 0 {
|
|
// Last byte
|
|
break
|
|
}
|
|
|
|
if read >= 4 {
|
|
return false
|
|
}
|
|
multiplier *= 128
|
|
}
|
|
return true
|
|
}
|