1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
|
/* packet-dji-uav.c
* Routines for the disassembly of the command protocol for the
* DJI Phantom 2 Vision+ UAV
* http://www.dji.com/product/phantom-2-vision-plus
* and possibly others.
*
* Copyright 2014,2015 Joerg Mayer (see AUTHORS file)
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/*
*/
#include "config.h"
#include <epan/packet.h>
/* TCP desegmentation */
#include "packet-tcp.h"
/* Request Response tracking */
#include <epan/conversation.h>
#include <epan/prefs.h>
void proto_register_djiuav(void);
void proto_reg_handoff_djiuav(void);
static dissector_handle_t djiuav_handle;
/* Enable desegmentation of djiuav over TCP */
static gboolean djiuav_desegment = TRUE;
/* Command/Response tracking */
typedef struct _djiuav_conv_info_t {
wmem_map_t *pdus;
} djiuav_conv_info_t;
typedef struct _djiuav_transaction_t {
guint16 seqno;
guint8 command;
guint32 request_frame;
guint32 reply_frame;
nstime_t request_time;
} djiuav_transaction_t;
/* Finally: Protocol specific stuff */
/* protocol handles */
static int proto_djiuav = -1;
/* ett handles */
static int ett_djiuav = -1;
/* hf elements */
static int hf_djiuav_magic = -1;
static int hf_djiuav_length = -1;
static int hf_djiuav_flags = -1;
static int hf_djiuav_seqno = -1;
static int hf_djiuav_cmd = -1;
static int hf_djiuav_checksum = -1;
#if 0
static int hf_djiuav_cmd04_unknown = -1;
static int hf_djiuav_resp04_unknown = -1;
#endif
static int hf_djiuav_cmd20_unknown = -1;
#if 0
static int hf_djiuav_resp20_unknown = -1;
#endif
static int hf_djiuav_cmdunk = -1;
static int hf_djiuav_respunk = -1;
static int hf_djiuav_extradata = -1;
/* hf request/response tracking */
static int hf_djiuav_response_in = -1;
static int hf_djiuav_response_to = -1;
static int hf_djiuav_response_time = -1;
#define PROTO_SHORT_NAME "DJIUAV"
#define PROTO_LONG_NAME "DJI UAV Drone Control Protocol"
#define PORT_DJIUAV 2001 /* Not IANA registered */
static const value_string djiuav_pdu_type[] = {
{ 0x20, "Set Time" },
{ 0, NULL }
};
static void
request_response_handling(tvbuff_t *tvb, packet_info *pinfo, proto_tree *djiuav_tree,
guint32 offset)
{
conversation_t *conversation;
djiuav_conv_info_t *djiuav_info;
djiuav_transaction_t *djiuav_trans;
guint16 seq_no;
gboolean is_cmd;
guint8 packet_type;
is_cmd = (pinfo->match_uint == pinfo->destport);
seq_no = tvb_get_letohs(tvb, offset + 4);
packet_type = tvb_get_guint8(tvb, offset + 6);
conversation = find_or_create_conversation(pinfo);
djiuav_info = (djiuav_conv_info_t *)conversation_get_proto_data(conversation, proto_djiuav);
if (!djiuav_info) {
djiuav_info = wmem_new(wmem_file_scope(), djiuav_conv_info_t);
djiuav_info->pdus=wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
conversation_add_proto_data(conversation, proto_djiuav, djiuav_info);
}
if (!pinfo->fd->visited) {
if (is_cmd) {
djiuav_trans=wmem_new(wmem_file_scope(), djiuav_transaction_t);
djiuav_trans->request_frame=pinfo->num;
djiuav_trans->reply_frame=0;
djiuav_trans->request_time=pinfo->abs_ts;
djiuav_trans->seqno=seq_no;
djiuav_trans->command=packet_type;
wmem_map_insert(djiuav_info->pdus, GUINT_TO_POINTER((guint)seq_no), (void *)djiuav_trans);
} else {
djiuav_trans=(djiuav_transaction_t *)wmem_map_lookup(djiuav_info->pdus, GUINT_TO_POINTER((guint)seq_no));
if (djiuav_trans) {
/* Special case: djiuav seems to send 0x24 replies with seqno 0 and without a request */
if (djiuav_trans->reply_frame == 0)
djiuav_trans->reply_frame=pinfo->num;
}
}
} else {
djiuav_trans=(djiuav_transaction_t *)wmem_map_lookup(djiuav_info->pdus, GUINT_TO_POINTER((guint)seq_no));
}
/* djiuav_trans may be 0 in case it's a reply without a matching request */
if (djiuav_tree && djiuav_trans) {
if (is_cmd) {
if (djiuav_trans->reply_frame) {
proto_item *it;
it = proto_tree_add_uint(djiuav_tree, hf_djiuav_response_in,
tvb, 0, 0, djiuav_trans->reply_frame);
proto_item_set_generated(it);
}
} else {
if (djiuav_trans->request_frame) {
proto_item *it;
nstime_t ns;
it = proto_tree_add_uint(djiuav_tree, hf_djiuav_response_to,
tvb, 0, 0, djiuav_trans->request_frame);
proto_item_set_generated(it);
nstime_delta(&ns, &pinfo->abs_ts, &djiuav_trans->request_time);
it = proto_tree_add_time(djiuav_tree, hf_djiuav_response_time, tvb, 0, 0, &ns);
proto_item_set_generated(it);
}
}
}
}
static int
dissect_djiuav_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
proto_item *ti;
proto_tree *djiuav_tree = NULL;
guint32 offset = 0;
guint32 pdu_length;
guint8 packet_type;
gboolean is_cmd;
is_cmd = (pinfo->match_uint == pinfo->destport);
packet_type = tvb_get_guint8(tvb, 6);
col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_SHORT_NAME);
col_add_str(pinfo->cinfo, COL_INFO, is_cmd?"C: ":"R: ");
col_append_str(pinfo->cinfo, COL_INFO, val_to_str(packet_type,
djiuav_pdu_type, "Type 0x%02x"));
ti = proto_tree_add_item(tree, proto_djiuav, tvb, offset, -1, ENC_NA);
djiuav_tree = proto_item_add_subtree(ti, ett_djiuav);
request_response_handling(tvb, pinfo, djiuav_tree, offset);
if (tree) {
proto_tree_add_item(djiuav_tree, hf_djiuav_magic, tvb, offset, 2,
ENC_BIG_ENDIAN);
offset += 2;
pdu_length = tvb_get_guint8(tvb, offset);
proto_tree_add_item(djiuav_tree, hf_djiuav_length, tvb, offset, 1,
ENC_NA);
offset += 1;
proto_tree_add_item(djiuav_tree, hf_djiuav_flags, tvb, offset, 1,
ENC_NA);
offset += 1;
proto_tree_add_item(djiuav_tree, hf_djiuav_seqno, tvb, offset, 2,
ENC_LITTLE_ENDIAN);
offset += 2;
proto_tree_add_item(djiuav_tree, hf_djiuav_cmd, tvb, offset, 1,
ENC_NA);
offset += 1;
if (is_cmd) { /* Command */
switch (packet_type) {
case 0x20: /* Set time */
/* FIXME: Properly decode this: year(lo) year(hi) month date hour minute second */
proto_tree_add_item(djiuav_tree, hf_djiuav_cmd20_unknown, tvb, offset, 7,
ENC_NA);
offset += 7;
break;
default:
proto_tree_add_item(djiuav_tree, hf_djiuav_cmdunk, tvb, offset, pdu_length - 8,
ENC_NA);
offset += (pdu_length - 8);
break;
}
} else { /* Response */
switch (packet_type) {
default:
proto_tree_add_item(djiuav_tree, hf_djiuav_respunk, tvb,
offset, pdu_length - 8, ENC_NA);
offset += (pdu_length - 8);
break;
}
}
if (offset < pdu_length - 1) { /* We guessed wrong about the cmd len */
proto_tree_add_item(djiuav_tree, hf_djiuav_extradata, tvb, offset,
pdu_length - 1 - offset, ENC_NA);
offset += pdu_length - 1 - offset;
}
/* FIXME: calculate XOR and validate transmitted value */
proto_tree_add_checksum(djiuav_tree, tvb, offset, hf_djiuav_checksum, -1, NULL, pinfo, 0, ENC_BIG_ENDIAN, PROTO_CHECKSUM_NO_FLAGS);
offset += 1;
}
return offset;
}
static gboolean
test_djiuav(tvbuff_t *tvb)
{
/* Minimum of 8 bytes, beginning with magic bytes 0x55BB */
if ( tvb_captured_length(tvb) < 8 /* Size of a command with empty data is at least 8 */
|| tvb_get_ntohs(tvb, 0) != 0x55BB
) {
return FALSE;
}
return TRUE;
}
/* Get the length of the full pdu */
static guint
get_djiuav_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
{
return tvb_get_guint8(tvb, offset + 2);
}
static int
dissect_djiuav_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
if ( !test_djiuav(tvb) ) {
return 0;
}
tcp_dissect_pdus(tvb, pinfo, tree, djiuav_desegment, 8,
get_djiuav_pdu_len, dissect_djiuav_pdu, data);
return tvb_captured_length(tvb);
}
void
proto_register_djiuav(void)
{
static hf_register_info hf[] = {
/* DJIUAV header */
{ &hf_djiuav_magic,
{ "Protocol Magic", "djiuav.magic", FT_UINT16, BASE_HEX, NULL,
0x0, NULL, HFILL }},
{ &hf_djiuav_length,
{ "PDU Length", "djiuav.length", FT_UINT8, BASE_HEX, NULL,
0x0, NULL, HFILL }},
{ &hf_djiuav_flags,
{ "Flags", "djiuav.flags", FT_UINT8, BASE_HEX, NULL,
0x0, NULL, HFILL }},
{ &hf_djiuav_seqno,
{ "Sequence No", "djiuav.seqno", FT_UINT16, BASE_DEC, NULL,
0x0, NULL, HFILL }},
{ &hf_djiuav_cmd,
{ "PDU Type", "djiuav.pdutype", FT_UINT8, BASE_HEX, VALS(djiuav_pdu_type),
0x0, NULL, HFILL }},
{ &hf_djiuav_checksum,
{ "Checksum", "djiuav.checksum", FT_UINT8, BASE_HEX, NULL,
0x0, NULL, HFILL }},
/* 0x04 */
#if 0
{ &hf_djiuav_cmd04_unknown,
{ "C04 Unknown", "djiuav.cmd04.unknown", FT_UINT8, BASE_HEX, NULL,
0x0, NULL, HFILL }},
{ &hf_djiuav_resp04_unknown,
{ "R04 Unknown", "djiuav.resp04.unknown", FT_UINT8, BASE_HEX, NULL,
0x0, NULL, HFILL }},
#endif
/* Set time */
{ &hf_djiuav_cmd20_unknown,
{ "Time in BCD", "djiuav.cmd04.bcdtime", FT_BYTES, BASE_NONE, NULL,
0x0, NULL, HFILL }},
#if 0
{ &hf_djiuav_resp20_unknown,
{ "R20 Unknown", "djiuav.resp04.unknown", FT_UINT8, BASE_HEX, NULL,
0x0, NULL, HFILL }},
#endif
/* CMD Unknown */
{ &hf_djiuav_cmdunk,
{ "C Unknown", "djiuav.cmd.unknown", FT_BYTES, BASE_NONE, NULL,
0x0, NULL, HFILL }},
/* RESP Unknown */
{ &hf_djiuav_respunk,
{ "R Unknown", "djiuav.resp.unknown", FT_BYTES, BASE_NONE, NULL,
0x0, NULL, HFILL }},
/* Extra Data (unexpected) */
{ &hf_djiuav_extradata,
{ "Unexpected", "djiuav.unexpected", FT_BYTES, BASE_NONE, NULL,
0x0, NULL, HFILL }},
/* Request - Response tracking */
{ &hf_djiuav_response_in,
{ "Response In", "djiuav.response_in", FT_FRAMENUM, BASE_NONE, NULL,
0x0, "Matching response in frame", HFILL }},
{ &hf_djiuav_response_to,
{ "Request In", "djiuav.response_to",
FT_FRAMENUM, BASE_NONE, NULL,
0x0, "Matching command in frame", HFILL }},
{ &hf_djiuav_response_time,
{ "Response Time", "djiuav.response_time",
FT_RELATIVE_TIME, BASE_NONE, NULL,
0x0, "Time between Command and matching Response", HFILL }},
};
static gint *ett[] = {
&ett_djiuav,
};
module_t *djiuav_module;
proto_djiuav = proto_register_protocol(PROTO_LONG_NAME, PROTO_SHORT_NAME, "djiuav");
proto_register_field_array(proto_djiuav, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
/* Preferences */
djiuav_module = prefs_register_protocol(proto_djiuav, NULL);
prefs_register_bool_preference(djiuav_module, "desegment",
"Reassemble DJIUAV messages",
"Whether DJIUAV should reassemble messages spanning multiple"
" TCP segments (required to get useful results)",
&djiuav_desegment);
djiuav_handle = register_dissector("djiuav", dissect_djiuav_static, proto_djiuav);
}
void
proto_reg_handoff_djiuav(void)
{
dissector_add_uint_with_preference("tcp.port", PORT_DJIUAV, djiuav_handle);
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 8
* tab-width: 8
* indent-tabs-mode: t
* End:
*
* vi: set shiftwidth=8 tabstop=8 noexpandtab:
* :indentSize=8:tabSize=8:noTabs=false:
*/
|