Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / wiretap / rtpdump.c
blob55aa53970a91f5efdf1d96df05bbe2a48e0111fa
1 /* rtpdump.c
3 * Wiretap Library
4 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
6 * Support for RTPDump file format
7 * Copyright (c) 2023 by David Perry <boolean263@protonmail.com>
9 * SPDX-License-Identifier: GPL-2.0-or-later
11 /* The rtpdump file format is the "dump" format as generated by rtpdump from
12 * <https://github.com/irtlab/rtptools>. It starts with an ASCII text header:
14 * #!rtpplay1.0 source_address/port\n
16 * followed by a binary header:
18 * typedef struct {
19 * struct timeval start; // start of recording (GMT)
20 * uint32_t source; // network source (multicast)
21 * uint16_t port; // UDP port
22 * } RD_hdr_t;
24 * Note that the SAME source address and port are included twice in
25 * this header, as seen here:
26 * <https://github.com/irtlab/rtptools/blob/9356740cb0c/rtpdump.c#L189>
28 * Wireshark can also generate rtpdump files, but it uses DIFFERENT addresses
29 * and ports in the text vs binary headers. See rtp_write_header() in
30 * ui/tap-rtp-common.c -- Wireshark puts the destination address and port
31 * in the text header, but the source address and port in the binary header.
33 * Wireshark may also generate the file with an IPv6 address in the text
34 * header, whereas rtpdump only supports IPv4 here. The binary header
35 * can't hold an IPv6 address without fully breaking compatibility with
36 * the rtptools project, so Wireshark truncates the address.
38 * Either way, each packet which follows is a RTP or RTCP packet of the form
40 * typedef struct {
41 * uint16_t length; // length of original packet, including header
42 * uint16_t plen; // actual header+payload length for RTP, 0 for RTCP
43 * uint32_t offset; // ms since the start of recording
44 * } RD_packet_t;
46 * followed by length bytes of packet data.
49 #include "config.h"
50 #include "rtpdump.h"
52 #include <wtap-int.h>
53 #include <file_wrappers.h>
54 #include <wsutil/exported_pdu_tlvs.h>
55 #include <wsutil/inet_addr.h>
56 #include <wsutil/nstime.h>
57 #include <wsutil/strtoi.h>
58 #include <wsutil/wslog.h>
59 #include <string.h>
61 /* NB. I've included the version string in the magic for stronger identification.
62 * If we add/change version support, we'll also need to edit:
63 * - wiretap/mime_file.c
64 * - resources/freedesktop/org.wireshark.Wireshark-mime.xml
65 * - epan/dissectors/file-rtpdump.c
67 #define RTP_MAGIC "#!rtpplay1.0 "
68 #define RTP_MAGIC_LEN 13
70 /* Reasonable maximum length for the RTP header (after the magic):
71 * - WS_INET6_ADDRSTRLEN characters for a IPv6 address
72 * - 1 for a slash
73 * - 5 characters for a port number
74 * - 1 character for a newline
75 * - 4 bytes for each of start seconds, start useconds, IPv4 address
76 * - 2 bytes for each of port, padding
77 * and 2 bytes of fudge factor, just in case.
79 #define RTP_HEADER_MAX_LEN 25+WS_INET6_ADDRSTRLEN
81 /* Reasonable buffer size for holding the Exported PDU headers:
82 * - 8 bytes for the port type header
83 * - 8 bytes for one port
84 * - 4+EXP_PDU_TAG_IPV6_LEN for one IPv6 address
85 * (same space would hold 2 IPv4 addresses with room to spare)
87 #define RTP_BUFFER_INIT_LEN 20+EXP_PDU_TAG_IPV6_LEN
89 static bool
90 rtpdump_read(wtap *wth, wtap_rec *rec, Buffer *buf,
91 int *err, char **err_info,
92 int64_t *data_offset);
94 static bool
95 rtpdump_seek_read(wtap *wth, int64_t seek_off,
96 wtap_rec *rec, Buffer *buf,
97 int *err, char **err_info);
99 static void
100 rtpdump_close(wtap *wth);
102 static int rtpdump_file_type_subtype = -1;
104 typedef union ip_addr_u {
105 ws_in4_addr ipv4;
106 ws_in6_addr ipv6;
107 } ip_addr_t;
109 typedef struct rtpdump_priv_s {
110 Buffer epdu_headers;
111 nstime_t start_time;
112 } rtpdump_priv_t;
114 void register_rtpdump(void);
116 wtap_open_return_val
117 rtpdump_open(wtap *wth, int *err, char **err_info)
119 uint8_t buf_magic[RTP_MAGIC_LEN];
120 char ch = '\0';
121 rtpdump_priv_t *priv = NULL;
122 ip_addr_t txt_addr;
123 ws_in4_addr bin_addr;
124 uint16_t txt_port = 0;
125 uint16_t bin_port = 0;
126 GString *header_str = NULL;
127 bool is_ipv6 = false;
128 bool got_ip = false;
129 nstime_t start_time = NSTIME_INIT_ZERO;
130 Buffer *buf;
132 if (!wtap_read_bytes(wth->fh, buf_magic, RTP_MAGIC_LEN, err, err_info)) {
133 return (*err == WTAP_ERR_SHORT_READ)
134 ? WTAP_OPEN_NOT_MINE
135 : WTAP_OPEN_ERROR;
137 if (strncmp(buf_magic, RTP_MAGIC, RTP_MAGIC_LEN) != 0) {
138 return WTAP_OPEN_NOT_MINE;
141 /* Parse the text header for an IP and port. */
142 header_str = g_string_sized_new(RTP_HEADER_MAX_LEN);
143 do {
144 if (!wtap_read_bytes(wth->fh, &ch, 1, err, err_info)) {
145 g_string_free(header_str, TRUE);
146 return (*err == WTAP_ERR_SHORT_READ)
147 ? WTAP_OPEN_NOT_MINE
148 : WTAP_OPEN_ERROR;
151 if (ch == '/') {
152 /* Everything up to now should be an IP address */
153 if (ws_inet_pton4(header_str->str, &txt_addr.ipv4)) {
154 is_ipv6 = false;
156 else if (ws_inet_pton6(header_str->str, &txt_addr.ipv6)) {
157 is_ipv6 = true;
159 else {
160 *err = WTAP_ERR_BAD_FILE;
161 *err_info = ws_strdup("rtpdump: bad IP in header text");
162 g_string_free(header_str, TRUE);
163 return WTAP_OPEN_ERROR;
166 got_ip = true;
167 g_string_truncate(header_str, 0);
169 else if (ch == '\n') {
170 if (!got_ip) {
171 *err = WTAP_ERR_BAD_FILE;
172 *err_info = ws_strdup("rtpdump: no IP in header text");
173 g_string_free(header_str, TRUE);
174 return WTAP_OPEN_ERROR;
176 if (!ws_strtou16(header_str->str, NULL, &txt_port)) {
177 *err = WTAP_ERR_BAD_FILE;
178 *err_info = ws_strdup("rtpdump: bad port in header text");
179 g_string_free(header_str, TRUE);
180 return WTAP_OPEN_ERROR;
182 break;
184 else if (g_ascii_isprint(ch)) {
185 g_string_append_c(header_str, ch);
187 else {
188 g_string_free(header_str, TRUE);
189 return WTAP_OPEN_NOT_MINE;
191 } while (ch != '\n');
193 g_string_free(header_str, TRUE);
195 if (!got_ip || txt_port == 0) {
196 *err = WTAP_ERR_BAD_FILE;
197 *err_info = ws_strdup("rtpdump: bad header text");
198 return WTAP_OPEN_ERROR;
201 /* Whether generated by rtpdump or Wireshark, the binary header
202 * has the source IP and port. If the text header has an IPv6 address,
203 * this address was likely truncated from an IPv6 address as well
204 * and is likely inaccurate, so we will ignore it.
207 #define FAIL G_STMT_START { \
208 return (*err == WTAP_ERR_SHORT_READ) \
209 ? WTAP_OPEN_NOT_MINE \
210 : WTAP_OPEN_ERROR; \
211 } G_STMT_END
213 if (!wtap_read_bytes(wth->fh, &start_time.secs, 4, err, err_info)) FAIL;
214 start_time.secs = g_ntohl(start_time.secs);
216 if (!wtap_read_bytes(wth->fh, &start_time.nsecs, 4, err, err_info)) FAIL;
217 start_time.nsecs = g_ntohl(start_time.nsecs) * 1000;
219 if (!wtap_read_bytes(wth->fh, &bin_addr, 4, err, err_info)) FAIL;
220 if (!wtap_read_bytes(wth->fh, &bin_port, 2, err, err_info)) FAIL;
221 bin_port = g_ntohs(bin_port);
223 /* Finally, padding */
224 if (!wtap_read_bytes(wth->fh, NULL, 2, err, err_info)) FAIL;
226 #undef FAIL
227 /* If we made it this far, we have all the info we need to generate
228 * most of the Exported PDU headers for every packet in this stream.
230 priv = g_new0(rtpdump_priv_t, 1);
231 priv->start_time = start_time;
232 buf = &priv->epdu_headers; /* shorthand */
233 ws_buffer_init(buf, RTP_BUFFER_INIT_LEN);
234 wtap_buffer_append_epdu_uint(buf, EXP_PDU_TAG_PORT_TYPE, EXP_PDU_PT_UDP);
235 if (is_ipv6) {
236 /* File must be generated by Wireshark. Text address is IPv6 destination,
237 * binary address is invalid and ignored here.
239 wtap_buffer_append_epdu_tag(buf, EXP_PDU_TAG_IPV6_DST, (const uint8_t *)&txt_addr.ipv6, EXP_PDU_TAG_IPV6_LEN);
240 wtap_buffer_append_epdu_uint(buf, EXP_PDU_TAG_DST_PORT, txt_port);
242 else if (txt_addr.ipv4 == bin_addr && txt_port == bin_port) {
243 /* File must be generated by rtpdump. Both addresses are IPv4 source. */
244 wtap_buffer_append_epdu_tag(buf, EXP_PDU_TAG_IPV4_SRC, (const uint8_t *)&bin_addr, EXP_PDU_TAG_IPV4_LEN);
245 wtap_buffer_append_epdu_uint(buf, EXP_PDU_TAG_SRC_PORT, bin_port);
247 else {
248 /* File must be generated by Wireshark. Text is IPv4 destination,
249 * binary is IPv4 source.
251 wtap_buffer_append_epdu_tag(buf, EXP_PDU_TAG_IPV4_DST, (const uint8_t *)&txt_addr.ipv4, EXP_PDU_TAG_IPV4_LEN);
252 wtap_buffer_append_epdu_uint(buf, EXP_PDU_TAG_DST_PORT, txt_port);
254 wtap_buffer_append_epdu_tag(buf, EXP_PDU_TAG_IPV4_SRC, (const uint8_t *)&bin_addr, EXP_PDU_TAG_IPV4_LEN);
255 wtap_buffer_append_epdu_uint(buf, EXP_PDU_TAG_SRC_PORT, bin_port);
258 wth->priv = (void *)priv;
259 wth->subtype_close = rtpdump_close;
260 wth->subtype_read = rtpdump_read;
261 wth->subtype_seek_read = rtpdump_seek_read;
262 wth->file_type_subtype = rtpdump_file_type_subtype;
263 wth->file_encap = WTAP_ENCAP_WIRESHARK_UPPER_PDU;
264 /* Starting timestamp has microsecond precision, but delta time
265 * between packets is only milliseconds.
267 wth->file_tsprec = WTAP_TSPREC_MSEC;
269 return WTAP_OPEN_MINE;
272 static bool
273 rtpdump_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec, Buffer *buf,
274 int *err, char **err_info)
276 rtpdump_priv_t *priv = (rtpdump_priv_t *)wth->priv;
277 nstime_t ts = NSTIME_INIT_ZERO;
278 uint32_t epdu_len = 0; /* length of the Exported PDU headers we add */
279 const uint8_t hdr_len = 8; /* Header comprised of the following 3 fields: */
280 uint16_t length; /* length of packet, including this header (may
281 be smaller than plen if not whole packet recorded) */
282 uint16_t plen; /* actual header+payload length for RTP, 0 for RTCP */
283 uint32_t offset; /* milliseconds since the start of recording */
285 if (!wtap_read_bytes_or_eof(fh, (void *)&length, 2, err, err_info)) return false;
286 length = g_ntohs(length);
287 if (!wtap_read_bytes(fh, (void *)&plen, 2, err, err_info)) return false;
288 plen = g_ntohs(plen);
289 if (!wtap_read_bytes(fh, (void *)&offset, 4, err, err_info)) return false;
290 offset = g_ntohl(offset);
292 /* Set length to remaining length of packet data */
293 length -= hdr_len;
295 ws_buffer_append_buffer(buf, &priv->epdu_headers);
296 if (plen == 0) {
297 /* RTCP sample */
298 plen = length;
299 wtap_buffer_append_epdu_string(buf, EXP_PDU_TAG_DISSECTOR_NAME, "rtcp");
301 else {
302 /* RTP sample */
303 wtap_buffer_append_epdu_string(buf, EXP_PDU_TAG_DISSECTOR_NAME, "rtp");
305 epdu_len = wtap_buffer_append_epdu_end(buf);
307 /* Offset is milliseconds since the start of recording */
308 ts.secs = offset / 1000;
309 ts.nsecs = (offset % 1000) * 1000000;
310 nstime_sum(&rec->ts, &priv->start_time, &ts);
311 rec->presence_flags |= WTAP_HAS_TS | WTAP_HAS_CAP_LEN;
312 rec->rec_header.packet_header.caplen = epdu_len + plen;
313 rec->rec_header.packet_header.len = epdu_len + length;
314 rec->rec_type = REC_TYPE_PACKET;
316 return wtap_read_packet_bytes(fh, buf, length, err, err_info);
319 static bool
320 rtpdump_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, char **err_info,
321 int64_t *data_offset)
323 *data_offset = file_tell(wth->fh);
324 return rtpdump_read_packet(wth, wth->fh, rec, buf, err, err_info);
327 static bool
328 rtpdump_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec,
329 Buffer *buf, int *err, char **err_info)
331 if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
332 return false;
333 return rtpdump_read_packet(wth, wth->random_fh, rec, buf, err, err_info);
336 static void
337 rtpdump_close(wtap *wth)
339 rtpdump_priv_t *priv = (rtpdump_priv_t *)wth->priv;
340 ws_buffer_free(&priv->epdu_headers);
343 static const struct supported_block_type rtpdump_blocks_supported[] = {
344 /* We support packet blocks, with no comments or other options. */
345 { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
348 static const struct file_type_subtype_info rtpdump_info = {
349 "RTPDump stream file", "rtpdump", "rtp", "rtpdump",
350 false, BLOCKS_SUPPORTED(rtpdump_blocks_supported),
351 NULL, NULL, NULL
354 void register_rtpdump(void)
356 rtpdump_file_type_subtype = wtap_register_file_type_subtype(&rtpdump_info);