mqtt: Add message decoding

Add support for configuring message decoding based on topic.
Matching criteria is equal-to, contains, starts-with, ends-with
or regular expression.

Change-Id: I677d869716eb1d2798974e2c65605a454421a66c
Reviewed-on: https://code.wireshark.org/review/24196
Petri-Dish: Stig Bjørlykke <stig@bjorlykke.org>
Tested-by: Petri Dish Buildbot
Reviewed-by: Stig Bjørlykke <stig@bjorlykke.org>
This commit is contained in:
Stig Bjørlykke 2017-10-31 11:28:05 +01:00
parent e8c7975b6f
commit 50c337f40a
1 changed files with 201 additions and 1 deletions

View File

@ -40,6 +40,8 @@
#include "config.h"
#include <epan/packet.h>
#include <epan/strutil.h>
#include <epan/uat.h>
#include <epan/dwarf.h>
#include "packet-tcp.h"
#include "packet-ssl.h"
@ -166,6 +168,32 @@ typedef struct {
guint8 runtime_proto_version;
} mqtt_conv;
typedef struct _mqtt_message_decode_t {
guint match_criteria;
char *topic_pattern;
GRegex *topic_regex;
char *payload_proto_name;
dissector_handle_t payload_proto;
} mqtt_message_decode_t;
#define MATCH_CRITERIA_EQUAL 0
#define MATCH_CRITERIA_CONTAINS 1
#define MATCH_CRITERIA_STARTS_WITH 2
#define MATCH_CRITERIA_ENDS_WITH 3
#define MATCH_CRITERIA_REGEX 4
static const value_string match_criteria[] = {
{ MATCH_CRITERIA_EQUAL, "Equal to" },
{ MATCH_CRITERIA_CONTAINS, "Contains" },
{ MATCH_CRITERIA_STARTS_WITH, "Starts with" },
{ MATCH_CRITERIA_ENDS_WITH, "Ends with" },
{ MATCH_CRITERIA_REGEX, "Regular Expression" },
{ 0, NULL }
};
static mqtt_message_decode_t *mqtt_message_decodes = NULL;
static guint num_mqtt_message_decodes = 0;
static dissector_handle_t mqtt_handle;
/* Initialize the protocol and registered fields */
@ -199,6 +227,7 @@ static int hf_mqtt_username = -1;
static int hf_mqtt_passwd_len = -1;
static int hf_mqtt_passwd = -1;
static int hf_mqtt_pubmsg = -1;
static int hf_mqtt_pubmsg_decoded = -1;
static int hf_mqtt_proto_len = -1;
static int hf_mqtt_proto_name = -1;
static int hf_mqtt_client_id_len = -1;
@ -238,12 +267,140 @@ static guint get_mqtt_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb,
return (guint)(GET_MQTT_PDU_LEN(msg_len, len_offset));
}
static void *mqtt_message_decode_copy_cb(void *dest, const void *orig, size_t len _U_)
{
const mqtt_message_decode_t *o = (const mqtt_message_decode_t *)orig;
mqtt_message_decode_t *d = (mqtt_message_decode_t *)dest;
d->topic_pattern = g_strdup(o->topic_pattern);
d->payload_proto_name = g_strdup(o->payload_proto_name);
return d;
}
static gboolean mqtt_message_decode_update_cb(void *record, char **error)
{
mqtt_message_decode_t *u = (mqtt_message_decode_t *)record;
if (u->topic_pattern == NULL || strlen(u->topic_pattern) == 0)
{
*error = g_strdup("Missing topic pattern");
return FALSE;
}
if (u->match_criteria == MATCH_CRITERIA_REGEX)
{
u->topic_regex = g_regex_new(u->topic_pattern, (GRegexCompileFlags) G_REGEX_OPTIMIZE, (GRegexMatchFlags) 0, NULL);
if (!u->topic_regex)
{
*error = g_strdup_printf("Invalid regex: %s", u->topic_pattern);
return FALSE;
}
}
return TRUE;
}
static void mqtt_message_decode_free_cb(void *record)
{
mqtt_message_decode_t *u = (mqtt_message_decode_t *)record;
g_free(u->topic_pattern);
if (u->topic_regex)
{
g_regex_unref(u->topic_regex);
}
g_free(u->payload_proto_name);
}
UAT_VS_DEF(message_decode, match_criteria, mqtt_message_decode_t, guint, MATCH_CRITERIA_EQUAL, "Equal to")
UAT_CSTRING_CB_DEF(message_decode, topic_pattern, mqtt_message_decode_t)
UAT_PROTO_DEF(message_decode, payload_proto, payload_proto, payload_proto_name, mqtt_message_decode_t)
static void mqtt_user_decode_message(proto_tree *tree, proto_tree *mqtt_tree, packet_info *pinfo, const guint8 *topic_str, tvbuff_t *msg_tvb)
{
dissector_handle_t payload_proto = NULL;
const gchar *proto_name = NULL;
size_t topic_str_len = strlen(topic_str);
size_t topic_pattern_len;
if (topic_str_len == 0)
{
/* No topic to match */
return;
}
for (guint i = 0; i < num_mqtt_message_decodes && !payload_proto; i++)
{
switch (mqtt_message_decodes[i].match_criteria)
{
case MATCH_CRITERIA_EQUAL:
if (strcmp(topic_str, mqtt_message_decodes[i].topic_pattern) == 0)
{
proto_name = mqtt_message_decodes[i].payload_proto_name;
payload_proto = mqtt_message_decodes[i].payload_proto;
}
break;
case MATCH_CRITERIA_CONTAINS:
if (strstr(topic_str, mqtt_message_decodes[i].topic_pattern))
{
proto_name = mqtt_message_decodes[i].payload_proto_name;
payload_proto = mqtt_message_decodes[i].payload_proto;
}
break;
case MATCH_CRITERIA_STARTS_WITH:
topic_pattern_len = strlen(mqtt_message_decodes[i].topic_pattern);
if ((topic_str_len >= topic_pattern_len) &&
strncmp(topic_str, mqtt_message_decodes[i].topic_pattern, topic_pattern_len) == 0)
{
proto_name = mqtt_message_decodes[i].payload_proto_name;
payload_proto = mqtt_message_decodes[i].payload_proto;
}
break;
case MATCH_CRITERIA_ENDS_WITH:
topic_pattern_len = strlen(mqtt_message_decodes[i].topic_pattern);
if ((topic_str_len >= topic_pattern_len) &&
strcmp(topic_str + (topic_str_len - topic_pattern_len), mqtt_message_decodes[i].topic_pattern) == 0)
{
proto_name = mqtt_message_decodes[i].payload_proto_name;
payload_proto = mqtt_message_decodes[i].payload_proto;
}
break;
case MATCH_CRITERIA_REGEX:
if (mqtt_message_decodes[i].topic_regex)
{
GMatchInfo *match_info = NULL;
g_regex_match(mqtt_message_decodes[i].topic_regex, topic_str, (GRegexMatchFlags) 0, &match_info);
if (g_match_info_matches(match_info))
{
proto_name = mqtt_message_decodes[i].payload_proto_name;
payload_proto = mqtt_message_decodes[i].payload_proto;
}
g_match_info_free(match_info);
}
break;
default:
/* Unknown match criteria */
break;
}
}
if (payload_proto)
{
proto_item *ti = proto_tree_add_string(mqtt_tree, hf_mqtt_pubmsg_decoded, msg_tvb, 0, -1, proto_name);
PROTO_ITEM_SET_GENERATED(ti);
call_dissector(payload_proto, msg_tvb, pinfo, tree);
}
}
/* Dissect the MQTT message */
static int dissect_mqtt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
guint8 mqtt_fixed_hdr;
guint8 mqtt_msg_type;
proto_item *ti;
const guint8 *topic_str;
proto_tree *mqtt_tree;
@ -431,7 +588,8 @@ static int dissect_mqtt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
offset += 2;
mqtt_msg_len -= 2;
proto_tree_add_item(mqtt_tree, hf_mqtt_topic, tvb, offset, mqtt_str_len, ENC_UTF_8|ENC_NA);
proto_tree_add_item_ret_string(mqtt_tree, hf_mqtt_topic, tvb, offset, mqtt_str_len, ENC_UTF_8|ENC_NA,
wmem_epan_scope(), &topic_str);
offset += mqtt_str_len;
mqtt_msg_len -= mqtt_str_len;
@ -443,6 +601,12 @@ static int dissect_mqtt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
mqtt_msg_len -= 2;
}
proto_tree_add_item(mqtt_tree, hf_mqtt_pubmsg, tvb, offset, mqtt_msg_len, ENC_UTF_8|ENC_NA);
if (num_mqtt_message_decodes > 0)
{
tvbuff_t *msg_tvb = tvb_new_subset_length(tvb, offset, mqtt_msg_len);
mqtt_user_decode_message(tree, mqtt_tree, pinfo, topic_str, msg_tvb);
}
break;
case MQTT_SUBSCRIBE:
@ -664,6 +828,10 @@ void proto_register_mqtt(void)
{ "Message", "mqtt.msg",
FT_STRING, BASE_NONE, NULL, 0,
NULL, HFILL }},
{ &hf_mqtt_pubmsg_decoded,
{ "Message decoded as", "mqtt.msg_decoded_as",
FT_STRING, BASE_NONE, NULL, 0,
NULL, HFILL }},
{ &hf_mqtt_proto_len,
{ "Protocol Name Length", "mqtt.proto_len",
FT_UINT16, BASE_DEC, NULL, 0,
@ -732,6 +900,31 @@ void proto_register_mqtt(void)
&ett_mqtt_conack_flags
};
static uat_field_t mqtt_message_decode_flds[] = {
UAT_FLD_VS(message_decode, match_criteria, "Match criteria", match_criteria, "Match criteria"),
UAT_FLD_CSTRING(message_decode, topic_pattern, "Topic pattern", "Pattern to match for the topic"),
UAT_FLD_PROTO(message_decode, payload_proto, "Payload protocol",
"Protocol to be used for the message part of the matching topic"),
UAT_END_FIELDS
};
uat_t *message_uat = uat_new("Message Decoding",
sizeof(mqtt_message_decode_t),
"mqtt_message_decoding",
TRUE,
&mqtt_message_decodes,
&num_mqtt_message_decodes,
UAT_AFFECTS_DISSECTION, /* affects dissection of packets, but not set of named fields */
"ChMQTTMessageDecoding",
mqtt_message_decode_copy_cb,
mqtt_message_decode_update_cb,
mqtt_message_decode_free_cb,
NULL,
NULL,
mqtt_message_decode_flds);
module_t *mqtt_module;
/* Register protocol names and descriptions */
proto_mqtt = proto_register_protocol("MQ Telemetry Transport Protocol", "MQTT", "mqtt");
@ -740,6 +933,13 @@ void proto_register_mqtt(void)
proto_register_field_array(proto_mqtt, hf_mqtt, array_length(hf_mqtt));
proto_register_subtree_array(ett_mqtt, array_length(ett_mqtt));
mqtt_module = prefs_register_protocol(proto_mqtt, NULL);
prefs_register_uat_preference(mqtt_module, "message_decode_table",
"Message Decoding",
"A table that enumerates custom message decodes to be used for a certain topic",
message_uat);
}
/*