/src/ndpi/src/lib/protocols/mqtt.c
Line | Count | Source |
1 | | /* |
2 | | * mqtt.c |
3 | | * |
4 | | * Copyright (C) 2016 Sorin Zamfir <sorin.zamfir@yahoo.com> |
5 | | * |
6 | | * This file is part of nDPI, an open source deep packet inspection |
7 | | * library based on the OpenDPI and PACE technology by ipoque GmbH |
8 | | * |
9 | | * nDPI is free software: you can redistribute it and/or modify |
10 | | * it under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation, either version 3 of the License, or |
12 | | * (at your option) any later version. |
13 | | * |
14 | | * nDPI is distributed in the hope that it will be useful, |
15 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | * GNU Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with nDPI. If not, see <http://www.gnu.org/licenses/>. |
21 | | * |
22 | | */ |
23 | | |
24 | | #include "ndpi_protocol_ids.h" |
25 | | |
26 | | #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_MQTT |
27 | | |
28 | | #include "ndpi_api.h" |
29 | | #include "ndpi_private.h" |
30 | | |
31 | | |
32 | | /** |
33 | | * The type of control messages in mqtt version 3.1.1 |
34 | | * see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1 |
35 | | */ |
36 | | enum MQTT_PACKET_TYPES { |
37 | | CONNECT = 1, |
38 | | CONNACK = 2, |
39 | | PUBLISH = 3, |
40 | | PUBACK = 4, |
41 | | PUBREC = 5, |
42 | | PUBREL = 6, |
43 | | PUBCOMP = 7, |
44 | | SUBSCRIBE = 8, |
45 | | SUBACK = 9, |
46 | | UNSUBSCRIBE = 10, |
47 | | UNSUBACK = 11, |
48 | | PINGREQ = 12, |
49 | | PINGRESP = 13, |
50 | | DISCONNECT = 14 |
51 | | }; |
52 | | |
53 | | /** |
54 | | * Entry point when protocol is identified. |
55 | | */ |
56 | | static void ndpi_int_mqtt_add_connection (struct ndpi_detection_module_struct *ndpi_struct, |
57 | | struct ndpi_flow_struct *flow) |
58 | 2.62k | { |
59 | 2.62k | ndpi_set_detected_protocol(ndpi_struct, &flow->core, |
60 | 2.62k | NDPI_PROTOCOL_MQTT,NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI); |
61 | 2.62k | NDPI_LOG_INFO(ndpi_struct, "found Mqtt\n"); |
62 | 2.62k | } |
63 | | |
64 | | static int64_t get_var_int(const unsigned char *buf, int buf_len, u_int8_t *num_bytes) |
65 | 2.79M | { |
66 | 2.79M | int i, multiplier = 1; |
67 | 2.79M | u_int32_t value = 0; |
68 | 2.79M | u_int8_t encodedByte; |
69 | | |
70 | 2.79M | *num_bytes= 0; |
71 | 5.57M | for (i = 0; i < 4; i++) { |
72 | 4.99M | if (i >= buf_len) |
73 | 51.3k | return -1; |
74 | 4.94M | (*num_bytes)++; |
75 | 4.94M | encodedByte = buf[i]; |
76 | 4.94M | value += ((encodedByte & 127) * multiplier); |
77 | 4.94M | if ((encodedByte & 128) == 0) |
78 | 2.16M | break; |
79 | 2.77M | multiplier *= 128; |
80 | 2.77M | } |
81 | 2.74M | return value; |
82 | 2.79M | } |
83 | | |
84 | | /** |
85 | | * Dissector function that searches Mqtt headers |
86 | | */ |
87 | | static void ndpi_search_mqtt(struct ndpi_detection_module_struct *ndpi_struct, |
88 | | struct ndpi_flow_struct *flow) |
89 | 3.17M | { |
90 | 3.17M | u_int8_t pt,flags, rl_len; |
91 | 3.17M | int64_t rl; |
92 | | |
93 | 3.17M | NDPI_LOG_DBG(ndpi_struct, "search Mqtt\n"); |
94 | 3.17M | struct ndpi_packet_struct *packet = &ndpi_struct->packet; |
95 | 3.17M | if (flow->core.packet_counter > 10) { |
96 | 93 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt .. mandatory header not found!\n"); |
97 | 93 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
98 | 93 | return; |
99 | 93 | } |
100 | | |
101 | 3.17M | NDPI_LOG_DBG2(ndpi_struct, "====>>>> Mqtt header: %4x%4x%4x%4x [len: %u]\n", |
102 | 3.17M | packet->payload_packet_len > 0 ? packet->payload[0] : '.', |
103 | 3.17M | packet->payload_packet_len > 1 ? packet->payload[1] : '.', |
104 | 3.17M | packet->payload_packet_len > 2 ? packet->payload[2] : '.', |
105 | 3.17M | packet->payload_packet_len > 3 ? packet->payload[3] : '.', |
106 | 3.17M | packet->payload_packet_len); |
107 | 3.17M | if (packet->payload_packet_len < 2) { |
108 | 371k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt .. mandatory header not found!\n"); |
109 | 371k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
110 | 371k | return; |
111 | 371k | } |
112 | | // we extract the remaining length |
113 | 2.79M | rl = get_var_int(&packet->payload[1], packet->payload_packet_len - 1, &rl_len); |
114 | 2.79M | if (rl < 0) { |
115 | 51.3k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt .. invalid length!\n"); |
116 | 51.3k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
117 | 51.3k | return; |
118 | 51.3k | } |
119 | 2.74M | NDPI_LOG_DBG(ndpi_struct, "Mqtt: msg_len %d\n", (unsigned long long)rl); |
120 | 2.74M | if (packet->payload_packet_len != rl + 1 + rl_len) { |
121 | 2.70M | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt .. maximum packet size exceeded!\n"); |
122 | 2.70M | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
123 | 2.70M | return; |
124 | 2.70M | } |
125 | | // we extract the packet type |
126 | 42.3k | pt = (u_int8_t) ((packet->payload[0] & 0xF0) >> 4); |
127 | 42.3k | NDPI_LOG_DBG2(ndpi_struct,"====>>>> Mqtt packet type: [%d]\n",pt); |
128 | 42.3k | if ((pt == 0) || (pt == 15)) { |
129 | 13.0k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt .. invalid packet type!\n"); |
130 | 13.0k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
131 | 13.0k | return; |
132 | 13.0k | } |
133 | | // we extract the flags |
134 | 29.3k | flags = (u_int8_t) (packet->payload[0] & 0x0F); |
135 | 29.3k | NDPI_LOG_DBG2(ndpi_struct,"====>>>> Mqtt flags type: [%d]\n",flags); |
136 | | // first stage verification |
137 | 29.3k | if (((pt == CONNECT) || (pt == CONNACK) || (pt == PUBACK) || (pt == PUBREC) || |
138 | 19.3k | (pt == PUBCOMP) || (pt == SUBACK) || (pt == UNSUBACK) || (pt == PINGREQ) || |
139 | 17.3k | (pt == PINGRESP) || (pt == DISCONNECT)) && (flags > 0)) { |
140 | 14.2k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid Packet-Flag combination flag!=0\n"); |
141 | 14.2k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
142 | 14.2k | return; |
143 | 14.2k | } |
144 | 15.0k | if (((pt == PUBREL) || (pt == SUBSCRIBE) || (pt == UNSUBSCRIBE)) && (flags != 2)) { |
145 | 5.99k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid Packet-Flag combination flag!=2\n"); |
146 | 5.99k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
147 | 5.99k | return; |
148 | 5.99k | } |
149 | 9.10k | NDPI_LOG_DBG2(ndpi_struct,"====>>>> Passed first stage of identification\n"); |
150 | | // second stage verification (no payload, just variable headers) |
151 | 9.10k | if ((pt == CONNACK) || (pt == PUBACK) || (pt == PUBREL) || |
152 | 7.90k | (pt == PUBREC) || (pt == PUBCOMP) || (pt == UNSUBACK)) { |
153 | 2.09k | if (packet->payload_packet_len != 4) { // these packets are always 4 bytes long |
154 | 1.64k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid Packet-Length < 4 \n"); |
155 | 1.64k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
156 | 1.64k | return; |
157 | 1.64k | } else { |
158 | 455 | NDPI_LOG_INFO(ndpi_struct, "found Mqtt CONNACK/PUBACK/PUBREL/PUBREC/PUBCOMP/UNSUBACK\n"); |
159 | 455 | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
160 | 455 | return; |
161 | 455 | } |
162 | 2.09k | } |
163 | 7.00k | if ((pt == PINGREQ) || (pt == PINGRESP) || (pt == DISCONNECT)) { |
164 | 726 | if (packet->payload_packet_len != 2) { // these packets are always 2 bytes long |
165 | 344 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid Packet-Length <2 \n"); |
166 | 344 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
167 | 344 | return; |
168 | 382 | } else { |
169 | 382 | NDPI_LOG_INFO(ndpi_struct, "found Mqtt PING/PINGRESP/DISCONNECT\n"); |
170 | 382 | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
171 | 382 | return; |
172 | 382 | } |
173 | 726 | } |
174 | 6.27k | NDPI_LOG_DBG2(ndpi_struct,"====>>>> Passed second stage of identification\n"); |
175 | | // third stage verification (payload) |
176 | 6.27k | if (pt == CONNECT) { |
177 | 266 | NDPI_LOG_DBG(ndpi_struct, "found Mqtt CONNECT\n"); |
178 | 266 | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
179 | 266 | return; |
180 | 266 | } |
181 | 6.01k | if (pt == PUBLISH) { |
182 | | // payload CAN be zero bytes length (section 3.3.3 of MQTT standard) |
183 | 4.83k | u_int8_t qos = (u_int8_t) (flags & 0x06) >> 1; |
184 | 4.83k | u_int8_t dup = (u_int8_t) (flags & 0x08) >> 3; |
185 | 4.83k | if (qos > 2) { // qos values possible are 0,1,2 |
186 | 798 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid PUBLISH qos\n"); |
187 | 798 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
188 | 798 | return; |
189 | 798 | } |
190 | 4.03k | if (qos == 0) { |
191 | 1.45k | if (dup != 0) { |
192 | 545 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid PUBLISH qos0 and dup combination\n"); |
193 | 545 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
194 | 545 | return; |
195 | 545 | } |
196 | 911 | if (packet->payload_packet_len < 5) { // at least topic (3Bytes + 2Bytes fixed header) |
197 | 544 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid PUBLISH qos0 size\n"); |
198 | 544 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
199 | 544 | return; |
200 | 544 | } |
201 | 911 | } |
202 | 2.94k | if ((qos == 1) || (qos == 2)) { |
203 | 2.58k | if (packet->payload_packet_len < 7 ) { // at least topic + pkt identifier (3Bytes + 2Bytes + 2Bytes fixed header) |
204 | 1.85k | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid PUBLISH qos1&2\n"); |
205 | 1.85k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
206 | 1.85k | return; |
207 | 1.85k | } |
208 | 2.58k | } |
209 | 1.09k | NDPI_LOG_INFO(ndpi_struct, "found Mqtt PUBLISH\n"); |
210 | 1.09k | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
211 | 1.09k | return; |
212 | 2.94k | } |
213 | 1.17k | if (pt == SUBSCRIBE) { |
214 | 412 | if (packet->payload_packet_len < 8) { // at least one topic+filter is required in the payload |
215 | 239 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid SUBSCRIBE\n"); |
216 | 239 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
217 | 239 | return; |
218 | 239 | } else { |
219 | 173 | NDPI_LOG_INFO(ndpi_struct, "found Mqtt SUBSCRIBE\n"); |
220 | 173 | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
221 | 173 | return; |
222 | 173 | } |
223 | 412 | } |
224 | 764 | if (pt == SUBACK ) { |
225 | 311 | if (packet->payload_packet_len <5 ) { // must have at least a response code |
226 | 196 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid SUBACK\n"); |
227 | 196 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
228 | 196 | return; |
229 | 196 | } else { |
230 | 115 | NDPI_LOG_INFO(ndpi_struct, "found Mqtt SUBACK\n"); |
231 | 115 | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
232 | 115 | return; |
233 | 115 | } |
234 | 311 | } |
235 | 453 | if (pt == UNSUBSCRIBE) { |
236 | 453 | if (packet->payload_packet_len < 7) { // at least a topic |
237 | 310 | NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt invalid UNSUBSCRIBE\n"); |
238 | 310 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
239 | 310 | return; |
240 | 310 | } else { |
241 | 143 | NDPI_LOG_INFO(ndpi_struct, "found Mqtt UNSUBSCRIBE\n"); |
242 | 143 | ndpi_int_mqtt_add_connection(ndpi_struct,flow); |
243 | 143 | return; |
244 | 143 | } |
245 | 453 | } |
246 | | /* We already checked every possible values of pt: we are never here */ |
247 | 453 | } |
248 | | /** |
249 | | * Entry point for the ndpi library |
250 | | */ |
251 | | void init_mqtt_dissector (struct ndpi_detection_module_struct *ndpi_struct) |
252 | 15.4k | { |
253 | 15.4k | ndpi_register_dissector("MQTT", ndpi_struct, |
254 | 15.4k | ndpi_search_mqtt, |
255 | 15.4k | NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, |
256 | 15.4k | DISSECTOR_LICENSE_LGPL, |
257 | 15.4k | 1, NDPI_PROTOCOL_MQTT); |
258 | 15.4k | } |
259 | | |
260 | | |