3 * Routines for rtpdump file dissection
4 * Copyright 2023, David Perry <boolean263@protonmail.com>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * This dissects the rtpdump file format as generated by Wireshark.
11 * See also https://wiki.wireshark.org/rtpdump
12 * This file format was created as part of rtptools:
13 * https://github.com/irtlab/rtptools
15 * SPDX-License-Identifier: GPL-2.0-or-later
20 #include <epan/packet.h>
21 #include <epan/expert.h>
22 #include <wsutil/strtoi.h>
23 #include <wsutil/inet_addr.h>
24 #include <wsutil/array.h>
26 void proto_register_rtpdump(void);
27 void proto_reg_handoff_rtpdump(void);
29 /* Initialize the protocol and registered fields */
30 static int proto_rtpdump
;
32 static int hf_rtpdump_text_header
;
33 static int hf_rtpdump_play_program
;
34 static int hf_rtpdump_version
;
35 static int hf_rtpdump_txt_ipv4
;
36 static int hf_rtpdump_txt_ipv6
;
37 static int hf_rtpdump_txt_port
;
39 static int hf_rtpdump_binary_header
;
40 static int hf_rtpdump_ts_sec
;
41 static int hf_rtpdump_ts_usec
;
42 static int hf_rtpdump_ts
;
43 static int hf_rtpdump_bin_addr
;
44 static int hf_rtpdump_bin_port
;
45 static int hf_rtpdump_padding
;
47 static int hf_rtpdump_pkt
;
48 static int hf_rtpdump_pkt_len
;
49 static int hf_rtpdump_pkt_plen
;
50 static int hf_rtpdump_pkt_offset
;
51 static int hf_rtpdump_pkt_data
;
53 /* Initialize the subtree pointers */
54 static int ett_rtpdump
;
55 static int ett_rtpdump_text_header
;
56 static int ett_rtpdump_binary_header
;
57 static int ett_rtpdump_pkt
;
59 static expert_field ei_rtpdump_unknown_program
;
60 static expert_field ei_rtpdump_unknown_version
;
61 static expert_field ei_rtpdump_bad_txt_addr
;
62 static expert_field ei_rtpdump_bad_txt_port
;
63 static expert_field ei_rtpdump_bin_ipv6
;
64 static expert_field ei_rtpdump_addrs_match
;
65 static expert_field ei_rtpdump_addrs_mismatch
;
66 static expert_field ei_rtpdump_caplen
;
68 /* Reasonable minimum length for the RTP header (including the magic):
69 * - 13 for "#!rtpplay1.0 "
70 * - WS_INET_ADDRSTRLEN characters for a destination IPv4 address
72 * - 3 characters for a destination port number
73 * - 1 character for a newline
74 * - 4 bytes for each of start seconds, start useconds, source IPv4
75 * - 2 bytes for each of source port, padding
77 #define RTP_HEADER_MIN_LEN 24+WS_INET_ADDRSTRLEN
80 dissect_rtpdump(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*parent_tree
, void *data _U_
)
82 proto_tree
*tree
, *subtree
;
84 int tvb_len
= tvb_captured_length(tvb
);
86 static const uint8_t shebang
[] = {'#', '!'};
87 static const char rtpplay
[] = "rtpplay";
88 static const char rtpver
[] = "1.0";
95 uint16_t txt_port
= 0;
96 uint32_t bin_port
= 0;
97 ws_in4_addr txt_ipv4
= 0;
98 ws_in6_addr txt_ipv6
= {0};
99 ws_in4_addr bin_ipv4
= 0;
100 bool txt_is_ipv6
= false;
101 nstime_t start_time
= NSTIME_INIT_ZERO
;
105 if (tvb_len
< RTP_HEADER_MIN_LEN
)
107 if (0 != tvb_memeql(tvb
, 0, shebang
, sizeof(shebang
)))
109 if (-1 == (eol
= tvb_find_uint8(tvb
, 0, -1, '\n')) ||
110 -1 == (slash
= tvb_find_uint8(tvb
, 0, eol
, '/')) ||
111 -1 == (space
= tvb_find_uint8(tvb
, 0, slash
, ' '))) {
115 ti
= proto_tree_add_item(parent_tree
, proto_rtpdump
, tvb
, offset
, -1, ENC_NA
);
116 tree
= proto_item_add_subtree(ti
, ett_rtpdump
);
118 /* Handle the text header */
119 ti
= proto_tree_add_item(tree
, hf_rtpdump_text_header
, tvb
, offset
, eol
+1, ENC_ASCII
);
120 subtree
= proto_item_add_subtree(ti
, ett_rtpdump_text_header
);
122 /* Get the program name */
124 for (i
= offset
; g_ascii_isalpha(tvb_get_uint8(tvb
, i
)); i
++)
126 ti
= proto_tree_add_item_ret_string(subtree
, hf_rtpdump_play_program
,
127 tvb
, offset
, i
-offset
, ENC_ASCII
,
128 pinfo
->pool
, (const uint8_t **)&str
);
129 if (0 != g_strcmp0(str
, rtpplay
)) {
130 expert_add_info(pinfo
, ti
, &ei_rtpdump_unknown_program
);
133 /* Get the program version */
135 ti
= proto_tree_add_item_ret_string(subtree
, hf_rtpdump_version
,
136 tvb
, offset
, space
-offset
, ENC_ASCII
,
137 pinfo
->pool
, (const uint8_t **)&str
);
138 if (0 != g_strcmp0(str
, rtpver
)) {
139 expert_add_info(pinfo
, ti
, &ei_rtpdump_unknown_version
);
142 /* Get the text IP */
144 str
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
, slash
-offset
, ENC_ASCII
);
145 if (ws_inet_pton4(str
, &txt_ipv4
)) {
146 proto_tree_add_ipv4(subtree
, hf_rtpdump_txt_ipv4
, tvb
, offset
, slash
-offset
, txt_ipv4
);
148 else if (ws_inet_pton6(str
, &txt_ipv6
)) {
150 proto_tree_add_ipv6(subtree
, hf_rtpdump_txt_ipv6
, tvb
, offset
, slash
-offset
, &txt_ipv6
);
153 proto_tree_add_expert(subtree
, pinfo
, &ei_rtpdump_bad_txt_addr
,
154 tvb
, offset
, eol
-offset
);
157 /* Get the text port */
159 str
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
, eol
-offset
, ENC_ASCII
);
160 if (ws_strtou16(str
, NULL
, &txt_port
)) {
161 proto_tree_add_uint(subtree
, hf_rtpdump_txt_port
, tvb
, offset
, eol
-offset
, txt_port
);
164 proto_tree_add_expert(subtree
, pinfo
, &ei_rtpdump_bad_txt_port
,
165 tvb
, offset
, eol
-offset
);
168 /* Handle the binary header */
170 ti
= proto_tree_add_item(tree
, hf_rtpdump_binary_header
, tvb
, offset
, 16, ENC_NA
);
171 subtree
= proto_item_add_subtree(ti
, ett_rtpdump_binary_header
);
173 proto_tree_add_item_ret_uint(subtree
, hf_rtpdump_ts_sec
, tvb
, offset
, 4, ENC_BIG_ENDIAN
,
174 (uint32_t *)&start_time
.secs
);
175 proto_tree_add_item_ret_uint(subtree
, hf_rtpdump_ts_usec
, tvb
, offset
+4, 4, ENC_BIG_ENDIAN
,
177 start_time
.nsecs
*= 1000;
178 ti
= proto_tree_add_time(subtree
, hf_rtpdump_ts
, tvb
, offset
, 8, &start_time
);
179 proto_item_set_generated(ti
);
182 ti
= proto_tree_add_item(subtree
, hf_rtpdump_bin_addr
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
183 /* Force internal representation to big-endian as per wsutil/inet_ipv4.h */
184 bin_ipv4
= g_htonl(tvb_get_uint32(tvb
, offset
, ENC_BIG_ENDIAN
));
186 proto_tree_add_item_ret_uint(subtree
, hf_rtpdump_bin_port
, tvb
, offset
, 2, ENC_BIG_ENDIAN
, &bin_port
);
188 proto_tree_add_item(subtree
, hf_rtpdump_padding
, tvb
, offset
, 2, ENC_NA
);
192 expert_add_info(pinfo
, ti
, &ei_rtpdump_bin_ipv6
);
193 expert_add_info(pinfo
, subtree
, &ei_rtpdump_addrs_mismatch
);
195 else if(bin_ipv4
== txt_ipv4
&& bin_port
== txt_port
) {
196 expert_add_info(pinfo
, subtree
, &ei_rtpdump_addrs_match
);
199 expert_add_info(pinfo
, subtree
, &ei_rtpdump_addrs_mismatch
);
202 /* Handle individual packets */
203 while (offset
< tvb_len
) {
204 pkt_length
= tvb_get_ntohs(tvb
, offset
);
205 ti
= proto_tree_add_item(tree
, hf_rtpdump_pkt
, tvb
, offset
, pkt_length
, ENC_NA
);
206 subtree
= proto_item_add_subtree(ti
, ett_rtpdump_pkt
);
207 proto_item_set_text(subtree
, "Packet %d", pkt_num
++);
211 proto_tree_add_item(subtree
, hf_rtpdump_pkt_len
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
214 ti
= proto_tree_add_item_ret_uint(subtree
, hf_rtpdump_pkt_plen
, tvb
, offset
, 2, ENC_BIG_ENDIAN
,
216 if (data_length
> pkt_length
) {
217 expert_add_info(pinfo
, ti
, &ei_rtpdump_caplen
);
221 proto_tree_add_item(subtree
, hf_rtpdump_pkt_offset
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
224 proto_tree_add_item(subtree
, hf_rtpdump_pkt_data
, tvb
, offset
, pkt_length
, ENC_NA
);
225 offset
+= pkt_length
;
232 dissect_rtpdump_heur(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*parent_tree
, void *data
)
234 return dissect_rtpdump(tvb
, pinfo
, parent_tree
, data
) > 0;
237 /****************** Register the protocol with Wireshark ******************/
240 proto_register_rtpdump(void)
242 static hf_register_info hf
[] = {
243 { &hf_rtpdump_text_header
,
244 { "Text header", "rtpdump.text_header",
245 FT_STRING
, BASE_NONE
, NULL
, 0x0,
248 { &hf_rtpdump_play_program
,
249 { "Play program", "rtpdump.play_program",
250 FT_STRING
, BASE_NONE
, NULL
, 0x0,
251 "Program to be used to play this stream", HFILL
}
253 { &hf_rtpdump_version
,
254 { "File format version", "rtpdump.version",
255 FT_STRING
, BASE_NONE
, NULL
, 0x0,
258 { &hf_rtpdump_txt_ipv4
,
259 { "Text IPv4 address", "rtpdump.txt_addr",
260 FT_IPv4
, BASE_NONE
, NULL
, 0x0,
263 { &hf_rtpdump_txt_ipv6
,
264 { "Text IPv6 address", "rtpdump.txt_addr",
265 FT_IPv6
, BASE_NONE
, NULL
, 0x0,
268 { &hf_rtpdump_txt_port
,
269 { "Text port", "rtpdump.txt_port",
270 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
273 { &hf_rtpdump_binary_header
,
274 { "Binary header", "rtpdump.binary_header",
275 FT_BYTES
, BASE_NONE
|BASE_NO_DISPLAY_VALUE
, NULL
, 0x0,
278 { &hf_rtpdump_ts_sec
,
279 { "Start time (seconds)", "rtpdump.ts.sec",
280 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
283 { &hf_rtpdump_ts_usec
,
284 { "Start time (microseconds)", "rtpdump.ts_usec",
285 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
289 { "Start time", "rtpdump.ts",
290 FT_ABSOLUTE_TIME
, ABSOLUTE_TIME_LOCAL
, NULL
, 0x0,
293 { &hf_rtpdump_bin_addr
,
294 { "Binary IPv4 address", "rtpdump.bin_addr",
295 FT_IPv4
, BASE_NONE
, NULL
, 0x0,
298 { &hf_rtpdump_bin_port
,
299 { "Binary port", "rtpdump.bin_port",
300 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
303 { &hf_rtpdump_padding
,
304 { "Padding", "rtpdump.padding",
305 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
309 { "Packet", "rtpdump.packet",
310 FT_BYTES
, BASE_NONE
|BASE_NO_DISPLAY_VALUE
, NULL
, 0x0,
313 { &hf_rtpdump_pkt_len
,
314 { "Packet length", "rtpdump.pkt_len",
315 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
316 "Total packet length", HFILL
}
318 { &hf_rtpdump_pkt_plen
,
319 { "Data length", "rtpdump.pkt_plen",
320 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
323 { &hf_rtpdump_pkt_offset
,
324 { "Time offset (milliseconds)", "rtpdump.pkt_offset",
325 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
326 "Time from start of capture", HFILL
}
328 { &hf_rtpdump_pkt_data
,
329 { "Data", "rtpdump.pkt_data",
330 FT_BYTES
, BASE_NONE
|BASE_NO_DISPLAY_VALUE
, NULL
, 0x0,
335 /* Setup protocol subtree array */
336 static int *ett
[] = {
338 &ett_rtpdump_text_header
,
339 &ett_rtpdump_binary_header
,
343 static ei_register_info ei
[] = {
344 { &ei_rtpdump_unknown_program
,
345 { "rtpdump.play_program.unknown", PI_PROTOCOL
, PI_WARN
,
346 "Playback program not the expected 'rtpplay', dissection may be incorrect", EXPFILL
}},
347 { &ei_rtpdump_unknown_version
,
348 { "rtpdump.version.unknown", PI_PROTOCOL
, PI_WARN
,
349 "Version not recognized, dissection may be incorrect", EXPFILL
}},
350 { &ei_rtpdump_bad_txt_addr
,
351 { "rtpdump.txt_addr.bad", PI_PROTOCOL
, PI_WARN
,
352 "Unparseable text address", EXPFILL
}},
353 { &ei_rtpdump_bad_txt_port
,
354 { "rtpdump.txt_port.bad", PI_PROTOCOL
, PI_WARN
,
355 "Unparseable text port", EXPFILL
}},
356 { &ei_rtpdump_bin_ipv6
,
357 { "rtpdump.bin_addr.ipv6", PI_PROTOCOL
, PI_NOTE
,
358 "Binary IPv4 address may be a truncated IPv6 address", EXPFILL
}},
359 { &ei_rtpdump_addrs_match
,
360 { "rtpdump.address.match", PI_PROTOCOL
, PI_CHAT
,
361 "Text and binary addresses and ports match -- file likely generated by rtpdump", EXPFILL
}},
362 { &ei_rtpdump_addrs_mismatch
,
363 { "rtpdump.address.mismatch", PI_PROTOCOL
, PI_CHAT
,
364 "Text and binary addresses and ports do not match -- file likely generated by wireshark", EXPFILL
}},
365 { &ei_rtpdump_caplen
,
366 { "rtpdump.pkt_plen.truncated", PI_PROTOCOL
, PI_NOTE
,
367 "Data was truncated during capture", EXPFILL
}},
370 expert_module_t
* expert_rtpdump
;
372 /* Register the protocol name and description */
373 proto_rtpdump
= proto_register_protocol("RTPDump file format", "rtpdump", "rtpdump");
375 /* Required function calls to register the header fields
376 * and subtrees used */
377 proto_register_field_array(proto_rtpdump
, hf
, array_length(hf
));
378 proto_register_subtree_array(ett
, array_length(ett
));
380 expert_rtpdump
= expert_register_protocol(proto_rtpdump
);
381 expert_register_field_array(expert_rtpdump
, ei
, array_length(ei
));
383 register_dissector("rtpdump", dissect_rtpdump
, proto_rtpdump
);
387 proto_reg_handoff_rtpdump(void)
389 heur_dissector_add("wtap_file", dissect_rtpdump_heur
, "RTPDump file", "rtpdump_wtap", proto_rtpdump
, HEURISTIC_ENABLE
);