/src/wireshark/epan/dissectors/packet-rtsp.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* packet-rtsp.c |
2 | | * Routines for RTSP packet disassembly (RFC 2326) |
3 | | * |
4 | | * Jason Lango <jal@netapp.com> |
5 | | * Liberally copied from packet-http.c, by Guy Harris <guy@alum.mit.edu> |
6 | | * |
7 | | * Wireshark - Network traffic analyzer |
8 | | * By Gerald Combs <gerald@wireshark.org> |
9 | | * Copyright 1998 Gerald Combs |
10 | | * |
11 | | * SPDX-License-Identifier: GPL-2.0-or-later |
12 | | * |
13 | | * References: |
14 | | * RTSP is defined in RFC 2326, https://tools.ietf.org/html/rfc2326 |
15 | | * https://www.iana.org/assignments/rsvp-parameters |
16 | | * RFC 7826 describes RTSP 2.0, and technically obsoletes RFC 2326. |
17 | | * However, in practice due to lack of backwards compatibility, it has |
18 | | * has seen limited adoption and this dissector does not attempt to |
19 | | * dissect it. RFC 7826 does, however, have some useful comments about |
20 | | * ambiguities and pitfalls in RFC 2326. |
21 | | */ |
22 | | |
23 | | #include "config.h" |
24 | | |
25 | | #include <stdio.h> /* for sscanf() */ |
26 | | |
27 | | #include <epan/packet.h> |
28 | | #include <epan/req_resp_hdrs.h> |
29 | | #include <epan/prefs.h> |
30 | | #include <epan/conversation.h> |
31 | | #include <epan/expert.h> |
32 | | #include <epan/strutil.h> |
33 | | #include <epan/tap-voip.h> |
34 | | #include <epan/stats_tree.h> |
35 | | #include <epan/addr_resolv.h> |
36 | | #include <wsutil/str_util.h> |
37 | | #include <wsutil/strtoi.h> |
38 | | #include <wsutil/array.h> |
39 | | |
40 | | #include "packet-rdt.h" |
41 | | #include "packet-rtp.h" |
42 | | #include "packet-rtcp.h" |
43 | | #include "packet-e164.h" |
44 | | #include "packet-rtsp.h" |
45 | | #include "packet-media-type.h" |
46 | | |
47 | | void proto_register_rtsp(void); |
48 | | |
49 | | static int rtsp_tap; |
50 | | |
51 | | /* http://www.iana.org/assignments/rtsp-parameters/rtsp-parameters.xml */ |
52 | | |
53 | | const value_string rtsp_status_code_vals[] = { |
54 | | { 100, "Continue" }, |
55 | | { 199, "Informational - Others" }, |
56 | | |
57 | | { 200, "OK"}, |
58 | | { 201, "Created"}, |
59 | | { 250, "Low on Storage Space"}, |
60 | | { 299, "Success - Others"}, |
61 | | |
62 | | { 300, "Multiple Choices"}, |
63 | | { 301, "Moved Permanently"}, |
64 | | { 302, "Moved Temporarily"}, |
65 | | { 303, "See Other"}, |
66 | | { 305, "Use Proxy"}, |
67 | | { 399, "Redirection - Others"}, |
68 | | |
69 | | { 400, "Bad Request"}, |
70 | | { 401, "Unauthorized"}, |
71 | | { 402, "Payment Required"}, |
72 | | { 403, "Forbidden"}, |
73 | | { 404, "Not Found"}, |
74 | | { 405, "Method Not Allowed"}, |
75 | | { 406, "Not Acceptable"}, |
76 | | { 407, "Proxy Authentication Required"}, |
77 | | { 408, "Request Timeout"}, |
78 | | { 410, "Gone"}, |
79 | | { 411, "Length Required"}, |
80 | | { 412, "Precondition Failed"}, |
81 | | { 413, "Request Entity Too Large"}, |
82 | | { 414, "Request-URI Too Long"}, |
83 | | { 415, "Unsupported Media Type"}, |
84 | | { 451, "Invalid Parameter"}, |
85 | | { 452, "Illegal Conference Identifier"}, |
86 | | { 453, "Not Enough Bandwidth"}, |
87 | | { 454, "Session Not Found"}, |
88 | | { 455, "Method Not Valid In This State"}, |
89 | | { 456, "Header Field Not Valid"}, |
90 | | { 457, "Invalid Range"}, |
91 | | { 458, "Parameter Is Read-Only"}, |
92 | | { 459, "Aggregate Operation Not Allowed"}, |
93 | | { 460, "Only Aggregate Operation Allowed"}, |
94 | | { 461, "Unsupported Transport"}, |
95 | | { 462, "Destination Unreachable"}, |
96 | | { 499, "Client Error - Others"}, |
97 | | |
98 | | { 500, "Internal Server Error"}, |
99 | | { 501, "Not Implemented"}, |
100 | | { 502, "Bad Gateway"}, |
101 | | { 503, "Service Unavailable"}, |
102 | | { 504, "Gateway Timeout"}, |
103 | | { 505, "RTSP Version not supported"}, |
104 | | { 551, "Option Not Support"}, |
105 | | { 599, "Server Error - Others"}, |
106 | | |
107 | | { 0, NULL} |
108 | | }; |
109 | | |
110 | | static int proto_rtsp; |
111 | | |
112 | | static int ett_rtsp; |
113 | | static int ett_rtspframe; |
114 | | static int ett_rtsp_method; |
115 | | |
116 | | static int hf_rtsp_request; |
117 | | static int hf_rtsp_response; |
118 | | static int hf_rtsp_response_in; |
119 | | static int hf_rtsp_response_to; |
120 | | static int hf_rtsp_content_type; |
121 | | static int hf_rtsp_content_length; |
122 | | static int hf_rtsp_method; |
123 | | static int hf_rtsp_url; |
124 | | static int hf_rtsp_status; |
125 | | static int hf_rtsp_session; |
126 | | static int hf_rtsp_transport; |
127 | | static int hf_rtsp_rdtfeaturelevel; |
128 | | static int hf_rtsp_cseq; |
129 | | static int hf_rtsp_content_base; |
130 | | static int hf_rtsp_content_location; |
131 | | static int hf_rtsp_X_Vig_Msisdn; |
132 | | static int hf_rtsp_magic; |
133 | | static int hf_rtsp_channel; |
134 | | static int hf_rtsp_length; |
135 | | static int hf_rtsp_data; |
136 | | |
137 | | static int voip_tap; |
138 | | |
139 | | static expert_field ei_rtsp_unknown_transport_type; |
140 | | static expert_field ei_rtsp_bad_server_port; |
141 | | static expert_field ei_rtsp_bad_client_port; |
142 | | static expert_field ei_rtsp_bad_interleaved_channel; |
143 | | static expert_field ei_rtsp_content_length_invalid; |
144 | | static expert_field ei_rtsp_rdtfeaturelevel_invalid; |
145 | | static expert_field ei_rtsp_cseq_invalid; |
146 | | static expert_field ei_rtsp_bad_server_ip_address; |
147 | | static expert_field ei_rtsp_bad_client_ip_address; |
148 | | |
149 | | static dissector_handle_t rtsp_handle; |
150 | | static dissector_handle_t rtp_handle; |
151 | | static dissector_handle_t rtp_rfc4571_handle; |
152 | | static dissector_handle_t rtcp_handle; |
153 | | static dissector_handle_t rdt_handle; |
154 | | static dissector_table_t media_type_dissector_table; |
155 | | static heur_dissector_list_t heur_subdissector_list; |
156 | | |
157 | | static const char *st_str_packets = "Total RTSP Packets"; |
158 | | static const char *st_str_requests = "RTSP Request Packets"; |
159 | | static const char *st_str_responses = "RTSP Response Packets"; |
160 | | static const char *st_str_resp_broken = "???: broken"; |
161 | | static const char *st_str_resp_100 = "1xx: Informational"; |
162 | | static const char *st_str_resp_200 = "2xx: Success"; |
163 | | static const char *st_str_resp_300 = "3xx: Redirection"; |
164 | | static const char *st_str_resp_400 = "4xx: Client Error"; |
165 | | static const char *st_str_resp_500 = "5xx: Server Error"; |
166 | | static const char *st_str_other = "Other RTSP Packets"; |
167 | | |
168 | | static int st_node_packets = -1; |
169 | | static int st_node_requests = -1; |
170 | | static int st_node_responses = -1; |
171 | | static int st_node_resp_broken = -1; |
172 | | static int st_node_resp_100 = -1; |
173 | | static int st_node_resp_200 = -1; |
174 | | static int st_node_resp_300 = -1; |
175 | | static int st_node_resp_400 = -1; |
176 | | static int st_node_resp_500 = -1; |
177 | | static int st_node_other = -1; |
178 | | |
179 | | static void |
180 | | rtsp_stats_tree_init(stats_tree* st) |
181 | 0 | { |
182 | 0 | st_node_packets = stats_tree_create_node(st, st_str_packets, 0, STAT_DT_INT, true); |
183 | 0 | st_node_requests = stats_tree_create_pivot(st, st_str_requests, st_node_packets); |
184 | 0 | st_node_responses = stats_tree_create_node(st, st_str_responses, st_node_packets, STAT_DT_INT, true); |
185 | 0 | st_node_resp_broken = stats_tree_create_node(st, st_str_resp_broken, st_node_responses, STAT_DT_INT, true); |
186 | 0 | st_node_resp_100 = stats_tree_create_node(st, st_str_resp_100, st_node_responses, STAT_DT_INT, true); |
187 | 0 | st_node_resp_200 = stats_tree_create_node(st, st_str_resp_200, st_node_responses, STAT_DT_INT, true); |
188 | 0 | st_node_resp_300 = stats_tree_create_node(st, st_str_resp_300, st_node_responses, STAT_DT_INT, true); |
189 | 0 | st_node_resp_400 = stats_tree_create_node(st, st_str_resp_400, st_node_responses, STAT_DT_INT, true); |
190 | 0 | st_node_resp_500 = stats_tree_create_node(st, st_str_resp_500, st_node_responses, STAT_DT_INT, true); |
191 | 0 | st_node_other = stats_tree_create_node(st, st_str_other, st_node_packets, STAT_DT_INT, false); |
192 | 0 | } |
193 | | |
194 | | /* RTSP/Packet Counter stats packet function */ |
195 | | static tap_packet_status |
196 | | rtsp_stats_tree_packet(stats_tree* st, packet_info* pinfo _U_, epan_dissect_t* edt _U_, const void* p, tap_flags_t flags _U_) |
197 | 0 | { |
198 | 0 | const rtsp_info_value_t *v = (const rtsp_info_value_t *)p; |
199 | 0 | unsigned i = v->response_code; |
200 | 0 | int resp_grp; |
201 | 0 | const char *resp_str; |
202 | 0 | static char str[64]; |
203 | |
|
204 | 0 | tick_stat_node(st, st_str_packets, 0, false); |
205 | |
|
206 | 0 | if (i) { |
207 | 0 | tick_stat_node(st, st_str_responses, st_node_packets, false); |
208 | |
|
209 | 0 | if ( (i<100)||(i>=600) ) { |
210 | 0 | resp_grp = st_node_resp_broken; |
211 | 0 | resp_str = st_str_resp_broken; |
212 | 0 | } else if (i<200) { |
213 | 0 | resp_grp = st_node_resp_100; |
214 | 0 | resp_str = st_str_resp_100; |
215 | 0 | } else if (i<300) { |
216 | 0 | resp_grp = st_node_resp_200; |
217 | 0 | resp_str = st_str_resp_200; |
218 | 0 | } else if (i<400) { |
219 | 0 | resp_grp = st_node_resp_300; |
220 | 0 | resp_str = st_str_resp_300; |
221 | 0 | } else if (i<500) { |
222 | 0 | resp_grp = st_node_resp_400; |
223 | 0 | resp_str = st_str_resp_400; |
224 | 0 | } else { |
225 | 0 | resp_grp = st_node_resp_500; |
226 | 0 | resp_str = st_str_resp_500; |
227 | 0 | } |
228 | |
|
229 | 0 | tick_stat_node(st, resp_str, st_node_responses, false); |
230 | |
|
231 | 0 | snprintf(str, sizeof(str),"%u %s",i,val_to_str(i,rtsp_status_code_vals, "Unknown (%d)")); |
232 | 0 | tick_stat_node(st, str, resp_grp, false); |
233 | 0 | } else if (v->request_method) { |
234 | 0 | stats_tree_tick_pivot(st,st_node_requests,v->request_method); |
235 | 0 | } else { |
236 | 0 | tick_stat_node(st, st_str_other, st_node_packets, false); |
237 | 0 | } |
238 | |
|
239 | 0 | return TAP_PACKET_REDRAW; |
240 | 0 | } |
241 | | void proto_reg_handoff_rtsp(void); |
242 | | |
243 | | /* |
244 | | * desegmentation of RTSP headers |
245 | | * (when we are over TCP or another protocol providing the desegmentation API) |
246 | | */ |
247 | | static bool rtsp_desegment_headers = true; |
248 | | |
249 | | /* |
250 | | * desegmentation of RTSP bodies |
251 | | * (when we are over TCP or another protocol providing the desegmentation API) |
252 | | * TODO let the user filter on content-type the bodies he wants desegmented |
253 | | */ |
254 | | static bool rtsp_desegment_body = true; |
255 | | |
256 | | /* http://www.iana.org/assignments/port-numbers lists two rtsp ports. |
257 | | * In Addition RTSP uses display port over Wi-Fi Display: 7236. |
258 | | */ |
259 | 14 | #define RTSP_TCP_PORT_RANGE "554,8554,7236" |
260 | | |
261 | | /* |
262 | | * Takes an array of bytes, assumed to contain a null-terminated |
263 | | * string, as an argument, and returns the length of the string - |
264 | | * i.e., the size of the array, minus 1 for the null terminator. |
265 | | */ |
266 | 27 | #define STRLEN_CONST(str) (sizeof (str) - 1) |
267 | | |
268 | 70 | #define RTSP_FRAMEHDR ('$') |
269 | | |
270 | | typedef struct { |
271 | | char *request_uri; |
272 | | uint32_t req_frame; |
273 | | uint32_t resp_frame; |
274 | | |
275 | | } rtsp_req_resp_t; |
276 | | |
277 | | typedef struct { |
278 | | dissector_handle_t dissector; |
279 | | } rtsp_interleaved_t; |
280 | | |
281 | 0 | #define RTSP_MAX_INTERLEAVED (256) |
282 | | |
283 | | /* |
284 | | * Careful about dynamically allocating memory in this structure (say |
285 | | * for dynamically increasing the size of the 'interleaved' array) - |
286 | | * the containing structure is garbage collected and contained |
287 | | * pointers will not be freed. |
288 | | * |
289 | | * XXX - This is wmem allocated now. Rather than a array of fixed size, |
290 | | * the array could be, e.g., a tree or map indexed by the channel. |
291 | | */ |
292 | | typedef struct { |
293 | | rtsp_interleaved_t interleaved[RTSP_MAX_INTERLEAVED]; |
294 | | wmem_map_t *req_resp_map; |
295 | | } rtsp_conversation_data_t; |
296 | | |
297 | | static rtsp_conversation_data_t* |
298 | | get_rtsp_conversation_data(conversation_t *conv, packet_info *pinfo) |
299 | 0 | { |
300 | 0 | rtsp_conversation_data_t *data; |
301 | 0 | if (conv == NULL) { |
302 | 0 | conv = find_or_create_conversation_strat(pinfo); |
303 | 0 | } |
304 | | |
305 | | /* Look for previous data */ |
306 | 0 | data = (rtsp_conversation_data_t *)conversation_get_proto_data(conv, proto_rtsp); |
307 | | |
308 | | /* Create new data if necessary */ |
309 | 0 | if (!data) |
310 | 0 | { |
311 | 0 | data = wmem_new0(wmem_file_scope(), rtsp_conversation_data_t); |
312 | 0 | data->req_resp_map = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal); |
313 | 0 | conversation_add_proto_data(conv, proto_rtsp, data); |
314 | 0 | } |
315 | |
|
316 | 0 | return data; |
317 | 0 | } |
318 | | |
319 | | static int |
320 | | dissect_rtspinterleaved(tvbuff_t *tvb, int offset, packet_info *pinfo, |
321 | | proto_tree *tree) |
322 | 3 | { |
323 | 3 | unsigned length_remaining; |
324 | 3 | proto_item *ti; |
325 | 3 | proto_tree *rtspframe_tree = NULL; |
326 | 3 | int orig_offset; |
327 | 3 | uint8_t rf_chan; /* interleaved channel id */ |
328 | 3 | uint16_t rf_len; /* packet length */ |
329 | 3 | tvbuff_t *next_tvb; |
330 | 3 | conversation_t *conv; |
331 | 3 | rtsp_conversation_data_t *data; |
332 | 3 | dissector_handle_t dissector; |
333 | | |
334 | | /* |
335 | | * This will throw an exception if we don't have any data left. |
336 | | * That's what we want. (See "tcp_dissect_pdus()", which is |
337 | | * similar.) |
338 | | */ |
339 | 3 | length_remaining = tvb_ensure_captured_length_remaining(tvb, offset); |
340 | | |
341 | | /* |
342 | | * Can we do reassembly? |
343 | | */ |
344 | 3 | if (rtsp_desegment_headers && pinfo->can_desegment) { |
345 | | /* |
346 | | * Yes - would an RTSP multiplexed header starting at |
347 | | * this offset be split across segment boundaries? |
348 | | */ |
349 | 0 | if (length_remaining < 4) { |
350 | | /* |
351 | | * Yes. Tell the TCP dissector where the data for |
352 | | * this message starts in the data it handed us and |
353 | | * that we need "some more data." Don't tell it |
354 | | * exactly how many bytes we need because if/when we |
355 | | * ask for even more (after the header) that will |
356 | | * break reassembly. |
357 | | */ |
358 | 0 | pinfo->desegment_offset = offset; |
359 | 0 | pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; |
360 | 0 | return -1; |
361 | 0 | } |
362 | 0 | } |
363 | | |
364 | | /* |
365 | | * Get the "$", channel, and length from the header. |
366 | | */ |
367 | 3 | orig_offset = offset; |
368 | 3 | rf_chan = tvb_get_uint8(tvb, offset+1); |
369 | 3 | rf_len = tvb_get_ntohs(tvb, offset+2); |
370 | | |
371 | | /* |
372 | | * Can we do reassembly? |
373 | | */ |
374 | 3 | if (rtsp_desegment_body && pinfo->can_desegment) { |
375 | | /* |
376 | | * Yes - is the header + encapsulated packet split |
377 | | * across segment boundaries? |
378 | | */ |
379 | 0 | if (length_remaining < 4U + rf_len) { |
380 | | /* |
381 | | * Yes. Tell the TCP dissector where the data |
382 | | * for this message starts in the data it handed |
383 | | * us, and how many more bytes we need, and return. |
384 | | */ |
385 | 0 | pinfo->desegment_offset = offset; |
386 | 0 | pinfo->desegment_len = 4U + rf_len - length_remaining; |
387 | 0 | return -1; |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | 3 | col_add_fstr(pinfo->cinfo, COL_INFO, |
392 | 3 | "Interleaved channel 0x%02x, %u bytes", |
393 | 3 | rf_chan, rf_len); |
394 | | |
395 | 3 | ti = proto_tree_add_protocol_format(tree, proto_rtsp, tvb, |
396 | 3 | offset, 4, |
397 | 3 | "RTSP Interleaved Frame, Channel: 0x%02x, %u bytes", |
398 | 3 | rf_chan, rf_len); |
399 | 3 | rtspframe_tree = proto_item_add_subtree(ti, ett_rtspframe); |
400 | | |
401 | 3 | proto_tree_add_item(rtspframe_tree, hf_rtsp_magic, tvb, offset, 1, ENC_BIG_ENDIAN); |
402 | | |
403 | 3 | offset += 1; |
404 | | |
405 | 3 | proto_tree_add_item(rtspframe_tree, hf_rtsp_channel, tvb, offset, 1, ENC_BIG_ENDIAN); |
406 | | |
407 | 3 | offset += 1; |
408 | | |
409 | 3 | proto_tree_add_item(rtspframe_tree, hf_rtsp_length, tvb, offset, 2, ENC_BIG_ENDIAN); |
410 | 3 | offset += 2; |
411 | | |
412 | | /* |
413 | | * We set the actual length of the tvbuff for the interleaved |
414 | | * stuff to the minimum of what's left in the tvbuff and the |
415 | | * length in the header. |
416 | | * |
417 | | * XXX - what if there's nothing left in the tvbuff? |
418 | | * We'd want a BoundsError exception to be thrown, so |
419 | | * that a Short Frame would be reported. |
420 | | */ |
421 | 3 | if (length_remaining > rf_len) |
422 | 2 | length_remaining = rf_len; |
423 | 3 | next_tvb = tvb_new_subset_length_caplen(tvb, offset, length_remaining, rf_len); |
424 | | |
425 | 3 | conv = find_conversation_pinfo_strat(pinfo, 0); |
426 | | |
427 | 3 | if (conv && |
428 | 3 | (data = (rtsp_conversation_data_t *)conversation_get_proto_data(conv, proto_rtsp)) && |
429 | | /* Add the following condition if it is not always true. |
430 | | rf_chan < RTSP_MAX_INTERLEAVED && |
431 | | */ |
432 | 3 | (dissector = data->interleaved[rf_chan].dissector)) { |
433 | 0 | call_dissector(dissector, next_tvb, pinfo, tree); |
434 | 3 | } else { |
435 | 3 | bool dissected = false; |
436 | 3 | heur_dtbl_entry_t *hdtbl_entry = NULL; |
437 | | |
438 | 3 | dissected = dissector_try_heuristic(heur_subdissector_list, |
439 | 3 | next_tvb, pinfo, tree, &hdtbl_entry, NULL); |
440 | | |
441 | 3 | if (!dissected) { |
442 | 2 | proto_tree_add_item(rtspframe_tree, hf_rtsp_data, tvb, offset, rf_len, ENC_NA); |
443 | 2 | } |
444 | 3 | } |
445 | | |
446 | 3 | offset += rf_len; |
447 | | |
448 | 3 | return offset - orig_offset; |
449 | 3 | } |
450 | | |
451 | | static char* process_rtsp_request(tvbuff_t *tvb, int offset, const unsigned char *data, |
452 | | size_t linelen, packet_info *pinfo, proto_tree *tree); |
453 | | |
454 | | static void process_rtsp_reply(tvbuff_t *tvb, int offset, const unsigned char *data, |
455 | | size_t linelen, packet_info *pinfo, proto_tree *tree); |
456 | | |
457 | | typedef enum { |
458 | | RTSP_REQUEST, |
459 | | RTSP_REPLY, |
460 | | RTSP_NOT_FIRST_LINE |
461 | | } rtsp_type_t; |
462 | | |
463 | | static const char *rtsp_methods[] = { |
464 | | "DESCRIBE", |
465 | | "ANNOUNCE", |
466 | | "GET_PARAMETER", |
467 | | "OPTIONS", |
468 | | "PAUSE", |
469 | | "PLAY", |
470 | | "RECORD", |
471 | | "REDIRECT", |
472 | | "SETUP", |
473 | | "SET_PARAMETER", |
474 | | "TEARDOWN" |
475 | | }; |
476 | | |
477 | 1.32k | #define RTSP_NMETHODS array_length(rtsp_methods) |
478 | | |
479 | | static bool |
480 | | is_rtsp_request_or_reply(const unsigned char *line, size_t linelen, rtsp_type_t *type, |
481 | | rtsp_info_value_t *rtsp_stat_info, wmem_allocator_t *pool) |
482 | 110 | { |
483 | 110 | unsigned ii; |
484 | 110 | const unsigned char *token, *next_token; |
485 | 110 | int tokenlen; |
486 | 110 | char response_chars[4]; |
487 | | |
488 | | /* Is this an RTSP reply? */ |
489 | 110 | if (linelen >= 5 && g_ascii_strncasecmp("RTSP/", line, 5) == 0) { |
490 | | /* |
491 | | * Yes. |
492 | | */ |
493 | 0 | *type = RTSP_REPLY; |
494 | | /* The first token is the version. */ |
495 | 0 | tokenlen = get_token_len(line, line+linelen, &token); |
496 | 0 | if (tokenlen != 0) { |
497 | | /* The next token is the status code. */ |
498 | 0 | tokenlen = get_token_len(token, line+linelen, &next_token); |
499 | 0 | if (tokenlen >= 3) { |
500 | 0 | memcpy(response_chars, token, 3); |
501 | 0 | response_chars[3] = '\0'; |
502 | 0 | ws_strtou32(response_chars, NULL, &rtsp_stat_info->response_code); |
503 | 0 | } |
504 | 0 | } |
505 | 0 | return true; |
506 | 0 | } |
507 | | |
508 | | /* |
509 | | * Is this an RTSP request? |
510 | | * Check whether the line begins with one of the RTSP request |
511 | | * methods. |
512 | | */ |
513 | 1.32k | for (ii = 0; ii < RTSP_NMETHODS; ii++) { |
514 | 1.21k | size_t len = strlen(rtsp_methods[ii]); |
515 | 1.21k | if (linelen >= len && |
516 | 1.21k | g_ascii_strncasecmp(rtsp_methods[ii], line, len) == 0 && |
517 | 1.21k | (len == linelen || g_ascii_isspace(line[len]))) |
518 | 0 | { |
519 | 0 | *type = RTSP_REQUEST; |
520 | 0 | rtsp_stat_info->request_method = |
521 | 0 | wmem_strndup(pool, rtsp_methods[ii], len+1); |
522 | 0 | return true; |
523 | 0 | } |
524 | 1.21k | } |
525 | | |
526 | | /* Wasn't a request or a response */ |
527 | 110 | *type = RTSP_NOT_FIRST_LINE; |
528 | 110 | return false; |
529 | 110 | } |
530 | | |
531 | | static const char rtsp_content_type[] = "Content-Type:"; |
532 | | static const char rtsp_transport[] = "Transport:"; |
533 | | static const char rtsp_sps_server_port[] = "server_port="; |
534 | | static const char rtsp_cps_server_port[] = "client_port="; |
535 | | static const char rtsp_sps_dest_addr[] = "dest_addr="; |
536 | | static const char rtsp_cps_src_addr[] = "src_addr="; |
537 | | static const char rtsp_rtp_udp_default[] = "rtp/avp"; |
538 | | static const char rtsp_rtp_udp[] = "rtp/avp/udp"; |
539 | | static const char rtsp_rtp_tcp[] = "rtp/avp/tcp"; |
540 | | static const char rtsp_rdt_feature_level[] = "RDTFeatureLevel"; |
541 | | static const char rtsp_real_rdt[] = "x-real-rdt/"; |
542 | | static const char rtsp_real_tng[] = "x-pn-tng/"; /* synonym for x-real-rdt */ |
543 | | static const char rtsp_inter[] = "interleaved="; |
544 | | static const char rtsp_cseq[] = "CSeq:"; |
545 | | static const char rtsp_content_base[] = "Content-Base:"; |
546 | | static const char rtsp_content_location[] = "Content-Location:"; |
547 | | |
548 | | static sdp_setup_info_t* |
549 | | rtsp_create_setup_info(packet_info *pinfo, const char* session_id, const char *base_uri) |
550 | 30 | { |
551 | 30 | sdp_setup_info_t *setup_info = NULL; |
552 | 30 | if (!PINFO_FD_VISITED(pinfo)) { |
553 | | // setup_info is only used on the first pass (by SDP or RTP) |
554 | 30 | setup_info = wmem_new0(pinfo->pool, sdp_setup_info_t); |
555 | 30 | setup_info->hf_id = hf_rtsp_session; |
556 | 30 | setup_info->hf_type = SDP_TRACE_ID_HF_TYPE_STR; |
557 | | /* The session is a mandatory, opaque string for the session - but |
558 | | * not necessarily available at the time of media initialization via SDP, |
559 | | * whether DESCRIBE or via HTTP or some other protocol. It is known at |
560 | | * the time of actual RTP setup by RTSP, though. |
561 | | * |
562 | | * wmem_strdup will return "<NULL>" when it is not available, which |
563 | | * shouldn't ever be used by SDP (due to the "control" media attribute) |
564 | | * but prevents a possible null dereference with, e.g., fuzzed captures. |
565 | | * |
566 | | * It's in file scope (unlike the setup_info struct itself) because the |
567 | | * SDP and RTP dissectors don't copy the string but use it directly. |
568 | | * We probably could store the session id copy in conversation data. |
569 | | */ |
570 | 30 | setup_info->trace_id.str = wmem_strdup(wmem_file_scope(), session_id); |
571 | 30 | setup_info->base_uri = base_uri; |
572 | 30 | } |
573 | | |
574 | 30 | return setup_info; |
575 | 30 | } |
576 | | |
577 | | static void |
578 | | rtsp_create_conversation(packet_info *pinfo, proto_item *ti, |
579 | | const unsigned char *line_begin, size_t line_len, |
580 | | int rdt_feature_level, |
581 | | rtsp_type_t rtsp_type_packet, |
582 | | sdp_setup_info_t *setup_info) |
583 | 0 | { |
584 | 0 | char buf[256]; |
585 | 0 | char *tmp; |
586 | 0 | bool rtp_udp_transport = false; |
587 | 0 | bool rtp_tcp_transport = false; |
588 | 0 | bool rdt_transport = false; |
589 | 0 | unsigned c_data_port, c_mon_port; |
590 | 0 | unsigned s_data_port, s_mon_port; |
591 | 0 | unsigned ipv4_1, ipv4_2, ipv4_3, ipv4_4; |
592 | 0 | bool is_video = false; /* FIX ME - need to indicate video or not */ |
593 | 0 | address src_addr; |
594 | 0 | address dst_addr; |
595 | 0 | uint32_t ip4_addr; |
596 | 0 | rtp_dyn_payload_t *rtp_dyn_payload = NULL; |
597 | |
|
598 | 0 | if (rtsp_type_packet != RTSP_REPLY) { |
599 | 0 | return; |
600 | 0 | } |
601 | | |
602 | 0 | src_addr=pinfo->src; |
603 | 0 | dst_addr=pinfo->dst; |
604 | | |
605 | | /* Copy line into buf */ |
606 | 0 | if (line_len > sizeof(buf) - 1) |
607 | 0 | { |
608 | | /* Don't overflow the buffer. */ |
609 | 0 | line_len = sizeof(buf) - 1; |
610 | 0 | } |
611 | 0 | memcpy(buf, line_begin, line_len); |
612 | 0 | buf[line_len] = '\0'; |
613 | | |
614 | | /* Get past "Transport:" and spaces */ |
615 | 0 | tmp = buf + STRLEN_CONST(rtsp_transport); |
616 | 0 | while (*tmp && g_ascii_isspace(*tmp)) |
617 | 0 | tmp++; |
618 | | |
619 | | /* Work out which transport type is here */ |
620 | 0 | if (g_ascii_strncasecmp(tmp, rtsp_rtp_udp, strlen(rtsp_rtp_udp)) == 0) |
621 | 0 | { |
622 | 0 | rtp_udp_transport = true; |
623 | 0 | } |
624 | 0 | else if (g_ascii_strncasecmp(tmp, rtsp_rtp_tcp, strlen(rtsp_rtp_tcp)) == 0) |
625 | 0 | { |
626 | 0 | rtp_tcp_transport = true; |
627 | 0 | } |
628 | 0 | else if (g_ascii_strncasecmp(tmp, rtsp_rtp_udp_default, strlen(rtsp_rtp_udp_default)) == 0) |
629 | 0 | { |
630 | 0 | rtp_udp_transport = true; |
631 | 0 | } |
632 | 0 | else if (g_ascii_strncasecmp(tmp, rtsp_real_rdt, strlen(rtsp_real_rdt)) == 0 || |
633 | 0 | g_ascii_strncasecmp(tmp, rtsp_real_tng, strlen(rtsp_real_tng)) == 0) |
634 | 0 | { |
635 | 0 | rdt_transport = true; |
636 | 0 | } |
637 | 0 | else |
638 | 0 | { |
639 | | /* Give up on unknown transport types */ |
640 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_unknown_transport_type); |
641 | 0 | return; |
642 | 0 | } |
643 | | |
644 | 0 | c_data_port = c_mon_port = 0; |
645 | 0 | s_data_port = s_mon_port = 0; |
646 | | |
647 | | /* Look for server port */ |
648 | 0 | if ((tmp = strstr(buf, rtsp_sps_server_port))) { |
649 | 0 | tmp += strlen(rtsp_sps_server_port); |
650 | 0 | if (sscanf(tmp, "%u-%u", &s_data_port, &s_mon_port) < 1) { |
651 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_port); |
652 | 0 | return; |
653 | 0 | } |
654 | 0 | } |
655 | 0 | else if ((tmp = strstr(buf, rtsp_sps_dest_addr))) { |
656 | 0 | tmp += strlen(rtsp_sps_dest_addr); |
657 | 0 | if (sscanf(tmp, "\":%u\"", &s_data_port) == 1) { |
658 | | /* :9 mean ignore */ |
659 | 0 | if (s_data_port == 9) { |
660 | 0 | s_data_port = 0; |
661 | 0 | } |
662 | 0 | } |
663 | 0 | else if (sscanf(tmp, "\"%u.%u.%u.%u:%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4, &s_data_port) == 5) { |
664 | 0 | char *tmp2; |
665 | 0 | char *tmp3; |
666 | | |
667 | | /* Skip leading " */ |
668 | 0 | tmp++; |
669 | 0 | tmp2=strstr(tmp,":"); |
670 | 0 | tmp3=g_strndup(tmp,tmp2-tmp); |
671 | 0 | if (!str_to_ip(tmp3, &ip4_addr)) { |
672 | 0 | g_free(tmp3); |
673 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_ip_address); |
674 | 0 | return; |
675 | 0 | } |
676 | 0 | set_address(&dst_addr, AT_IPv4, 4, &ip4_addr); |
677 | 0 | g_free(tmp3); |
678 | 0 | } |
679 | 0 | else if (sscanf(tmp, "\"%u.%u.%u.%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4) == 4) { |
680 | 0 | char *tmp2; |
681 | 0 | char *tmp3; |
682 | | |
683 | | /* Skip leading " */ |
684 | 0 | tmp++; |
685 | 0 | tmp2=strstr(tmp,"\""); |
686 | 0 | tmp3=g_strndup(tmp,tmp2-tmp); |
687 | 0 | if (!str_to_ip(tmp3, &ip4_addr)) { |
688 | 0 | g_free(tmp3); |
689 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_ip_address); |
690 | 0 | return; |
691 | 0 | } |
692 | 0 | set_address(&dst_addr, AT_IPv4, 4, &ip4_addr); |
693 | 0 | g_free(tmp3); |
694 | 0 | } |
695 | 0 | else |
696 | 0 | { |
697 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_port); |
698 | 0 | return; |
699 | 0 | } |
700 | 0 | } |
701 | | |
702 | | |
703 | | /* Look for client port */ |
704 | 0 | if ((tmp = strstr(buf, rtsp_cps_server_port))) { |
705 | 0 | tmp += strlen(rtsp_cps_server_port); |
706 | 0 | if (sscanf(tmp, "%u-%u", &c_data_port, &c_mon_port) < 1) { |
707 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_client_port); |
708 | 0 | return; |
709 | 0 | } |
710 | 0 | } |
711 | 0 | else if ((tmp = strstr(buf, rtsp_cps_src_addr))) { |
712 | 0 | tmp += strlen(rtsp_cps_src_addr); |
713 | 0 | if (sscanf(tmp, "\"%u.%u.%u.%u:%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4, &c_data_port) == 5) { |
714 | 0 | char *tmp2; |
715 | 0 | char *tmp3; |
716 | | |
717 | | /* Skip leading " */ |
718 | 0 | tmp++; |
719 | 0 | tmp2=strstr(tmp,":"); |
720 | 0 | tmp3=g_strndup(tmp,tmp2-tmp); |
721 | 0 | if (!str_to_ip(tmp3, &ip4_addr)) { |
722 | 0 | g_free(tmp3); |
723 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_client_ip_address); |
724 | 0 | return; |
725 | 0 | } |
726 | 0 | set_address(&src_addr, AT_IPv4, 4, &ip4_addr); |
727 | 0 | g_free(tmp3); |
728 | 0 | } |
729 | 0 | } |
730 | | |
731 | 0 | if (setup_info && setup_info->base_uri) { |
732 | 0 | rtp_dyn_payload = sdp_get_rtsp_media_desc(setup_info->base_uri); |
733 | 0 | } |
734 | | |
735 | | /* Deal with RTSP TCP-interleaved conversations. */ |
736 | 0 | tmp = strstr(buf, rtsp_inter); |
737 | 0 | if (tmp != NULL) { |
738 | 0 | rtsp_conversation_data_t *data; |
739 | 0 | unsigned s_data_chan, s_mon_chan; |
740 | 0 | int i; |
741 | | |
742 | | /* Move tmp to beyond interleaved string */ |
743 | 0 | tmp += strlen(rtsp_inter); |
744 | | /* Look for channel number(s) */ |
745 | 0 | i = sscanf(tmp, "%u-%u", &s_data_chan, &s_mon_chan); |
746 | 0 | if (i < 1) |
747 | 0 | { |
748 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_interleaved_channel); |
749 | 0 | return; |
750 | 0 | } |
751 | | |
752 | | /* At least data channel present, look for conversation (presumably TCP) */ |
753 | 0 | data = get_rtsp_conversation_data(NULL, pinfo); |
754 | | |
755 | | /* XXX - This doesn't set up the rtp conversation data, including RTP |
756 | | * dynamic payload types and setup info. Two possible approaches: |
757 | | * 1) The RTP dissector have a function that attaches dynamic payload |
758 | | * type and setup info to the TCP conversation but does *not* set |
759 | | * the conversation dissector (TCP needs to call the RTSP dissector |
760 | | * first for interleaved data). |
761 | | * 2) Define a CONVERSATION_RTSP type and change the RTP dissector to |
762 | | * do something other than only look for conversations that match |
763 | | * conversation_pt_to_conversation_type(pinfo->ptype). |
764 | | * |
765 | | * The former needs to attach a "bundled" rtp_dyn_payload_t that |
766 | | * includes mapping for the payload types of all possible channels; |
767 | | * this usually happens when RTSP is used because the media descriptor |
768 | | * port is usually 0, but we'd want to ensure it. (It also would not |
769 | | * work if multiple sessions were SETUP simultaneously and media |
770 | | * descriptors with different meanings for the same RTP dynamic payload |
771 | | * type were PLAYed on different interleaved channels simulateously.) |
772 | | */ |
773 | | |
774 | | /* Now set the dissector handle of the interleaved channel |
775 | | according to the transport protocol used */ |
776 | 0 | if (rtp_tcp_transport) |
777 | 0 | { |
778 | 0 | if (s_data_chan < RTSP_MAX_INTERLEAVED) { |
779 | 0 | data->interleaved[s_data_chan].dissector = |
780 | 0 | rtp_handle; |
781 | 0 | } |
782 | 0 | if (i > 1 && s_mon_chan < RTSP_MAX_INTERLEAVED) { |
783 | 0 | data->interleaved[s_mon_chan].dissector = |
784 | 0 | rtcp_handle; |
785 | 0 | } |
786 | 0 | } |
787 | 0 | else if (rdt_transport) |
788 | 0 | { |
789 | 0 | if (s_data_chan < RTSP_MAX_INTERLEAVED) { |
790 | 0 | data->interleaved[s_data_chan].dissector = |
791 | 0 | rdt_handle; |
792 | 0 | } |
793 | 0 | } |
794 | 0 | return; |
795 | 0 | } |
796 | | |
797 | | /* Noninterleaved options follow */ |
798 | | /* |
799 | | * We only want to match on the destination address, not the |
800 | | * source address, because the server might send back a packet |
801 | | * from an address other than the address to which its client |
802 | | * sent the packet, so we construct a conversation with no |
803 | | * second address. |
804 | | */ |
805 | 0 | else if (rtp_udp_transport) |
806 | 0 | { |
807 | | /* RTP only if indicated */ |
808 | 0 | if (c_data_port) |
809 | 0 | { |
810 | 0 | srtp_add_address(pinfo, PT_UDP, &dst_addr, c_data_port, s_data_port, |
811 | 0 | "RTSP", pinfo->num, is_video, rtp_dyn_payload, NULL, setup_info); |
812 | 0 | } |
813 | 0 | else if (s_data_port) |
814 | 0 | { |
815 | 0 | srtp_add_address(pinfo, PT_UDP, &src_addr, s_data_port, 0, |
816 | 0 | "RTSP", pinfo->num, is_video, rtp_dyn_payload, NULL, setup_info); |
817 | 0 | } |
818 | | |
819 | | /* RTCP only if indicated */ |
820 | 0 | if (c_mon_port) |
821 | 0 | { |
822 | 0 | rtcp_add_address(pinfo, &pinfo->dst, c_mon_port, s_mon_port, |
823 | 0 | "RTSP", pinfo->num); |
824 | 0 | } |
825 | 0 | } |
826 | 0 | else if (rtp_tcp_transport) |
827 | 0 | { |
828 | | /* RTP only if indicated */ |
829 | 0 | srtp_add_address(pinfo, PT_TCP, &src_addr, c_data_port, s_data_port, |
830 | 0 | "RTSP", pinfo->num, is_video, rtp_dyn_payload, NULL, setup_info); |
831 | 0 | } |
832 | 0 | else if (rdt_transport) |
833 | 0 | { |
834 | | /* Real Data Transport */ |
835 | 0 | rdt_add_address(pinfo, &pinfo->dst, c_data_port, s_data_port, |
836 | 0 | "RTSP", rdt_feature_level); |
837 | 0 | } |
838 | 0 | return; |
839 | 0 | } |
840 | | |
841 | | static const char rtsp_content_length[] = "Content-Length:"; |
842 | | |
843 | | static int |
844 | | rtsp_get_content_length(const unsigned char *line_begin, size_t line_len) |
845 | 0 | { |
846 | 0 | char buf[256]; |
847 | 0 | char *tmp; |
848 | 0 | int32_t content_length; |
849 | 0 | const char *p; |
850 | 0 | const char *up; |
851 | |
|
852 | 0 | if (line_len > sizeof(buf) - 1) { |
853 | | /* |
854 | | * Don't overflow the buffer. |
855 | | */ |
856 | 0 | line_len = sizeof(buf) - 1; |
857 | 0 | } |
858 | 0 | memcpy(buf, line_begin, line_len); |
859 | 0 | buf[line_len] = '\0'; |
860 | |
|
861 | 0 | tmp = buf + STRLEN_CONST(rtsp_content_length); |
862 | 0 | while (*tmp && g_ascii_isspace(*tmp)) |
863 | 0 | tmp++; |
864 | 0 | ws_strtoi32(tmp, &p, &content_length); |
865 | 0 | up = p; |
866 | 0 | if (up == tmp || (*up != '\0' && !g_ascii_isspace(*up))) |
867 | 0 | return -1; /* not a valid number */ |
868 | 0 | return content_length; |
869 | 0 | } |
870 | | |
871 | | static const char rtsp_Session[] = "Session:"; |
872 | | static const char rtsp_X_Vig_Msisdn[] = "X-Vig-Msisdn"; |
873 | | |
874 | | static int |
875 | | dissect_rtspmessage(tvbuff_t *tvb, int offset, packet_info *pinfo, |
876 | | proto_tree *tree) |
877 | 37 | { |
878 | 37 | proto_tree *rtsp_tree = NULL; |
879 | 37 | proto_tree *req_tree = NULL; |
880 | 37 | proto_tree *sub_tree = NULL; |
881 | 37 | proto_item *ti_top = NULL; |
882 | 37 | proto_item *ti = NULL; |
883 | 37 | const unsigned char *line; |
884 | 37 | int next_offset; |
885 | 37 | const unsigned char *linep, *lineend; |
886 | 37 | int orig_offset; |
887 | 37 | int first_linelen, linelen; |
888 | 37 | int line_end_offset; |
889 | 37 | int colon_offset; |
890 | 37 | bool is_request_or_reply; |
891 | 37 | bool body_requires_content_len; |
892 | 37 | bool saw_req_resp_or_header; |
893 | 37 | unsigned char c; |
894 | 37 | rtsp_type_t rtsp_type_packet; |
895 | 37 | rtsp_type_t rtsp_type_line; |
896 | 37 | bool is_header; |
897 | 37 | int datalen; |
898 | 37 | int content_length; |
899 | 37 | int reported_datalen; |
900 | 37 | int value_offset; |
901 | 37 | int value_len; |
902 | 37 | e164_info_t e164_info; |
903 | 37 | int rdt_feature_level = 0; |
904 | 37 | char *media_type_str_lower_case = NULL; |
905 | 37 | int semi_colon_offset; |
906 | 37 | int par_end_offset; |
907 | 37 | char *frame_label = NULL; |
908 | 37 | char *session_id = NULL; |
909 | 37 | voip_packet_info_t *stat_info = NULL; |
910 | 37 | bool cseq_valid = false; |
911 | 37 | uint32_t cseq = 0; |
912 | 37 | char *content_base = NULL; |
913 | 37 | char *content_location = NULL; |
914 | 37 | char *request_uri = NULL; |
915 | 37 | char *base_uri = NULL; |
916 | 37 | const char *transport_line = NULL; |
917 | 37 | int transport_linelen; |
918 | 37 | sdp_setup_info_t *setup_info = NULL; |
919 | 37 | rtsp_info_value_t *rtsp_stat_info; |
920 | | |
921 | 37 | rtsp_stat_info = wmem_new(pinfo->pool, rtsp_info_value_t); |
922 | 37 | rtsp_stat_info->framenum = pinfo->num; |
923 | 37 | rtsp_stat_info->response_code = 0; |
924 | 37 | rtsp_stat_info->request_method = NULL; |
925 | 37 | rtsp_stat_info->request_uri = NULL; |
926 | 37 | rtsp_stat_info->rtsp_host = NULL; |
927 | | |
928 | | /* |
929 | | * Is this a request or response? |
930 | | * |
931 | | * Note that "tvb_find_line_end()" will return a value that |
932 | | * is not longer than what's in the buffer, so the |
933 | | * "tvb_get_ptr()" call won't throw an exception. |
934 | | */ |
935 | 37 | first_linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, false); |
936 | | |
937 | | /* |
938 | | * Is the first line a request or response? |
939 | | */ |
940 | 37 | line = tvb_get_ptr(tvb, offset, first_linelen); |
941 | 37 | is_request_or_reply = is_rtsp_request_or_reply(line, first_linelen, |
942 | 37 | &rtsp_type_packet, rtsp_stat_info, pinfo->pool); |
943 | 37 | if (is_request_or_reply) { |
944 | | /* |
945 | | * Yes, it's a request or response. |
946 | | * Do header desegmentation if we've been told to, |
947 | | * and do body desegmentation if we've been told to and |
948 | | * we find a Content-Length header. |
949 | | * |
950 | | * RFC 7826, Section 18.17. requires Content-Length and |
951 | | * assumes zero if missing. |
952 | | */ |
953 | 0 | if (!req_resp_hdrs_do_reassembly(tvb, offset, pinfo, |
954 | 0 | rtsp_desegment_headers, rtsp_desegment_body, false, NULL, |
955 | 0 | NULL, NULL)) { |
956 | | /* |
957 | | * More data needed for desegmentation. |
958 | | */ |
959 | 0 | return -1; |
960 | 0 | } |
961 | 0 | } |
962 | | |
963 | | /* |
964 | | * RFC 2326 says that a content length must be specified |
965 | | * in requests that have a body, although section 4.4 speaks |
966 | | * of a server closing the connection indicating the end of |
967 | | * a reply body. |
968 | | * |
969 | | * To support pipelining, we check if line behind blank line |
970 | | * looks like RTSP header. If so, we process rest of packet with |
971 | | * RTSP loop. |
972 | | * |
973 | | * If no, we assume that an absent content length in a request means |
974 | | * that we don't have a body, and that an absent content length |
975 | | * in a reply means that the reply body runs to the end of |
976 | | * the connection. If the first line is neither, we assume |
977 | | * that whatever follows a blank line should be treated as a |
978 | | * body; there's not much else we can do, as we're jumping |
979 | | * into the message in the middle. |
980 | | * |
981 | | * XXX - if there was no Content-Length entity header, we should |
982 | | * accumulate all data until the end of the connection. |
983 | | * That'd require that the TCP dissector call subdissectors |
984 | | * for all frames with FIN, even if they contain no data, |
985 | | * which would require subdissectors to deal intelligently |
986 | | * with empty segments. |
987 | | */ |
988 | 37 | if (rtsp_type_packet == RTSP_REQUEST) |
989 | 0 | body_requires_content_len = true; |
990 | 37 | else |
991 | 37 | body_requires_content_len = false; |
992 | | |
993 | 37 | line = tvb_get_ptr(tvb, offset, first_linelen); |
994 | 37 | if (is_request_or_reply) { |
995 | 0 | if ( rtsp_type_packet == RTSP_REPLY ) { |
996 | 0 | frame_label = wmem_strdup_printf(pinfo->pool, |
997 | 0 | "Reply: %s", format_text(pinfo->pool, line, first_linelen)); |
998 | 0 | } |
999 | 0 | else { |
1000 | 0 | frame_label = format_text(pinfo->pool, line, first_linelen); |
1001 | 0 | } |
1002 | 0 | } |
1003 | | |
1004 | 37 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTSP"); |
1005 | | /* |
1006 | | * Put the first line from the buffer into the summary |
1007 | | * if it's an RTSP request or reply (but leave out the |
1008 | | * line terminator). |
1009 | | * Otherwise, just call it a continuation. |
1010 | | * |
1011 | | * Note that "tvb_find_line_end()" will return a value that |
1012 | | * is not longer than what's in the buffer, so the |
1013 | | * "tvb_get_ptr()" call won't throw an exception. |
1014 | | */ |
1015 | 37 | if (is_request_or_reply) |
1016 | 0 | if ( rtsp_type_packet == RTSP_REPLY ) { |
1017 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Reply: "); |
1018 | 0 | col_append_str(pinfo->cinfo, COL_INFO, |
1019 | 0 | format_text(pinfo->pool, line, first_linelen)); |
1020 | 0 | } |
1021 | 0 | else { |
1022 | 0 | col_add_str(pinfo->cinfo, COL_INFO, |
1023 | 0 | format_text(pinfo->pool, line, first_linelen)); |
1024 | 0 | } |
1025 | | |
1026 | 37 | else |
1027 | 37 | col_set_str(pinfo->cinfo, COL_INFO, "Continuation"); |
1028 | | |
1029 | 37 | orig_offset = offset; |
1030 | 37 | if (tree) { |
1031 | 37 | ti_top = proto_tree_add_item(tree, proto_rtsp, tvb, offset, -1, |
1032 | 37 | ENC_NA); |
1033 | 37 | rtsp_tree = proto_item_add_subtree(ti_top, ett_rtsp); |
1034 | 37 | } |
1035 | | |
1036 | | /* |
1037 | | * We haven't yet seen a Content-Length header. |
1038 | | */ |
1039 | 37 | content_length = -1; |
1040 | | |
1041 | | /* |
1042 | | * Process the packet data, a line at a time. |
1043 | | */ |
1044 | 37 | saw_req_resp_or_header = false; /* haven't seen anything yet */ |
1045 | 46 | while (tvb_offset_exists(tvb, offset)) { |
1046 | | /* |
1047 | | * We haven't yet concluded that this is a header. |
1048 | | */ |
1049 | 43 | is_header = false; |
1050 | | |
1051 | | /* |
1052 | | * Find the end of the line. |
1053 | | */ |
1054 | 43 | linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, false); |
1055 | 43 | if (linelen < 0) |
1056 | 0 | return -1; |
1057 | 43 | line_end_offset = offset + linelen; |
1058 | | /* |
1059 | | * colon_offset may be -1 |
1060 | | */ |
1061 | 43 | colon_offset = tvb_find_uint8(tvb, offset, linelen, ':'); |
1062 | | |
1063 | | |
1064 | | /* |
1065 | | * Get a buffer that refers to the line. |
1066 | | */ |
1067 | 43 | line = tvb_get_ptr(tvb, offset, linelen); |
1068 | 43 | lineend = line + linelen; |
1069 | | |
1070 | | /* |
1071 | | * OK, does it look like an RTSP request or response? |
1072 | | */ |
1073 | 43 | is_request_or_reply = is_rtsp_request_or_reply(line, linelen, &rtsp_type_line, |
1074 | 43 | rtsp_stat_info, pinfo->pool); |
1075 | 43 | if (is_request_or_reply) |
1076 | 0 | goto is_rtsp; |
1077 | | |
1078 | | /* |
1079 | | * No. Does it look like a blank line (as would appear |
1080 | | * at the end of an RTSP request)? |
1081 | | */ |
1082 | 43 | if (linelen == 0) |
1083 | 3 | goto is_rtsp; /* Yes. */ |
1084 | | |
1085 | | /* |
1086 | | * No. Does it look like a header? |
1087 | | */ |
1088 | 40 | linep = line; |
1089 | 489 | while (linep < lineend) { |
1090 | 487 | c = *linep++; |
1091 | | |
1092 | | /* |
1093 | | * This must be a CHAR, and must not be a CTL, to be part |
1094 | | * of a token; that means it must be printable ASCII. |
1095 | | * |
1096 | | * XXX - what about leading LWS on continuation |
1097 | | * lines of a header? |
1098 | | */ |
1099 | 487 | if (!g_ascii_isprint(c)) |
1100 | 17 | break; |
1101 | | |
1102 | 470 | switch (c) { |
1103 | | |
1104 | 1 | case '(': |
1105 | 2 | case ')': |
1106 | 3 | case '<': |
1107 | 4 | case '>': |
1108 | 6 | case '@': |
1109 | 6 | case ',': |
1110 | 7 | case ';': |
1111 | 8 | case '\\': |
1112 | 9 | case '"': |
1113 | 9 | case '/': |
1114 | 10 | case '[': |
1115 | 10 | case ']': |
1116 | 11 | case '?': |
1117 | 12 | case '=': |
1118 | 12 | case '{': |
1119 | 12 | case '}': |
1120 | | /* |
1121 | | * It's a tspecial, so it's not |
1122 | | * part of a token, so it's not |
1123 | | * a field name for the beginning |
1124 | | * of a header. |
1125 | | */ |
1126 | 12 | goto not_rtsp; |
1127 | | |
1128 | 1 | case ':': |
1129 | | /* |
1130 | | * This ends the token; we consider |
1131 | | * this to be a header. |
1132 | | */ |
1133 | 1 | is_header = true; |
1134 | 1 | goto is_rtsp; |
1135 | | |
1136 | 8 | case ' ': |
1137 | 8 | case '\t': |
1138 | | /* |
1139 | | * LWS (RFC-2616, 4.2); continue the previous |
1140 | | * header. |
1141 | | */ |
1142 | 8 | goto is_rtsp; |
1143 | 470 | } |
1144 | 470 | } |
1145 | | |
1146 | | /* |
1147 | | * We haven't seen the colon, but everything else looks |
1148 | | * OK for a header line. |
1149 | | * |
1150 | | * If we've already seen an RTSP request or response |
1151 | | * line, or a header line, and we're at the end of |
1152 | | * the tvbuff, we assume this is an incomplete header |
1153 | | * line. (We quit this loop after seeing a blank line, |
1154 | | * so if we've seen a request or response line, or a |
1155 | | * header line, this is probably more of the request |
1156 | | * or response we're presumably seeing. There is some |
1157 | | * risk of false positives, but the same applies for |
1158 | | * full request or response lines or header lines, |
1159 | | * although that's less likely.) |
1160 | | * |
1161 | | * We throw an exception in that case, by checking for |
1162 | | * the existence of the next byte after the last one |
1163 | | * in the line. If it exists, "tvb_ensure_bytes_exist()" |
1164 | | * throws no exception, and we fall through to the |
1165 | | * "not RTSP" case. If it doesn't exist, |
1166 | | * "tvb_ensure_bytes_exist()" will throw the appropriate |
1167 | | * exception. |
1168 | | */ |
1169 | 19 | if (saw_req_resp_or_header) |
1170 | 5 | tvb_ensure_bytes_exist(tvb, offset, linelen + 1); |
1171 | | |
1172 | 27 | not_rtsp: |
1173 | | /* |
1174 | | * We don't consider this part of an RTSP request or |
1175 | | * reply, so we don't display it. |
1176 | | */ |
1177 | 27 | break; |
1178 | | |
1179 | 12 | is_rtsp: |
1180 | | /* |
1181 | | * Process this line. |
1182 | | */ |
1183 | 12 | if (linelen == 0) { |
1184 | | /* |
1185 | | * This is a blank line, which means that |
1186 | | * whatever follows it isn't part of this |
1187 | | * request or reply. |
1188 | | */ |
1189 | 3 | proto_tree_add_format_text(rtsp_tree, tvb, offset, next_offset - offset); |
1190 | 3 | offset = next_offset; |
1191 | 3 | break; |
1192 | 3 | } |
1193 | | |
1194 | | /* |
1195 | | * Not a blank line - either a request, a reply, or a header |
1196 | | * line. |
1197 | | */ |
1198 | 9 | saw_req_resp_or_header = true; |
1199 | | |
1200 | 9 | switch (rtsp_type_line) |
1201 | 9 | { |
1202 | 0 | case RTSP_REQUEST: |
1203 | | /* Add a tree for this request */ |
1204 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_request, tvb, offset, |
1205 | 0 | (int) (next_offset - offset), |
1206 | 0 | tvb_format_text(pinfo->pool, tvb, offset, (int) (next_offset - offset))); |
1207 | 0 | req_tree = proto_item_add_subtree(ti, ett_rtsp_method); |
1208 | 0 | request_uri = process_rtsp_request(tvb, offset, line, linelen, pinfo, req_tree); |
1209 | 0 | break; |
1210 | | |
1211 | 0 | case RTSP_REPLY: |
1212 | | /* Add a tree for this response */ |
1213 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_response, tvb, offset, |
1214 | 0 | (int) (next_offset - offset), |
1215 | 0 | tvb_format_text(pinfo->pool, tvb, offset, (int) (next_offset - offset))); |
1216 | 0 | req_tree = proto_item_add_subtree(ti, ett_rtsp_method); |
1217 | 0 | process_rtsp_reply(tvb, offset, line, linelen, pinfo, req_tree); |
1218 | 0 | break; |
1219 | | |
1220 | 9 | case RTSP_NOT_FIRST_LINE: |
1221 | | /* Drop through, it may well be a header line */ |
1222 | 9 | break; |
1223 | 9 | } |
1224 | | |
1225 | 9 | if (is_header) |
1226 | 1 | { |
1227 | | /* We know that colon_offset must be set */ |
1228 | | |
1229 | | /* Skip whitespace after the colon. */ |
1230 | 1 | value_offset = colon_offset + 1; |
1231 | 1 | while ((value_offset < line_end_offset) && |
1232 | 1 | ((c = tvb_get_uint8(tvb, value_offset)) == ' ' || c == '\t')) |
1233 | 0 | { |
1234 | 0 | value_offset++; |
1235 | 0 | } |
1236 | 1 | value_len = line_end_offset - value_offset; |
1237 | | |
1238 | | /* |
1239 | | * Process some headers specially. |
1240 | | */ |
1241 | 1 | #define HDR_MATCHES(header) \ |
1242 | 9 | ( (size_t)linelen > STRLEN_CONST(header) && \ |
1243 | 9 | g_ascii_strncasecmp(line, (header), STRLEN_CONST(header)) == 0) |
1244 | | |
1245 | 1 | if (HDR_MATCHES(rtsp_transport)) |
1246 | 0 | { |
1247 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_transport, tvb, |
1248 | 0 | offset, linelen, |
1249 | 0 | tvb_format_text(pinfo->pool, tvb, value_offset, |
1250 | 0 | value_len)); |
1251 | | |
1252 | | /* Setup the conversation after parsing all the headers. */ |
1253 | 0 | transport_line = line; |
1254 | 0 | transport_linelen = linelen; |
1255 | 1 | } else if (HDR_MATCHES(rtsp_content_type)) |
1256 | 0 | { |
1257 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_content_type, |
1258 | 0 | tvb, offset, linelen, |
1259 | 0 | tvb_format_text(pinfo->pool, tvb, value_offset, |
1260 | 0 | value_len)); |
1261 | |
|
1262 | 0 | offset = offset + (int)STRLEN_CONST(rtsp_content_type); |
1263 | | /* Skip wsp */ |
1264 | 0 | offset = tvb_skip_wsp(tvb, offset, value_len); |
1265 | 0 | semi_colon_offset = tvb_find_uint8(tvb, value_offset, value_len, ';'); |
1266 | 0 | if ( semi_colon_offset != -1) { |
1267 | | /* m-parameter present */ |
1268 | 0 | par_end_offset = tvb_skip_wsp_return(tvb, semi_colon_offset-1); |
1269 | 0 | value_len = par_end_offset - offset; |
1270 | 0 | } |
1271 | |
|
1272 | 0 | media_type_str_lower_case = ascii_strdown_inplace( |
1273 | 0 | (char *)tvb_get_string_enc(pinfo->pool, tvb, offset, value_len, ENC_ASCII)); |
1274 | 1 | } else if (HDR_MATCHES(rtsp_content_length)) |
1275 | 0 | { |
1276 | 0 | uint32_t clength; |
1277 | 0 | bool clength_valid; |
1278 | 0 | clength_valid = ws_strtou32(tvb_format_text(pinfo->pool, tvb, value_offset, value_len), |
1279 | 0 | NULL, &clength); |
1280 | 0 | ti = proto_tree_add_uint(rtsp_tree, hf_rtsp_content_length, |
1281 | 0 | tvb, offset, linelen, clength); |
1282 | 0 | if (!clength_valid) |
1283 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_content_length_invalid); |
1284 | | |
1285 | | /* |
1286 | | * Only the amount specified by the |
1287 | | * Content-Length: header should be treated |
1288 | | * as payload. |
1289 | | */ |
1290 | 0 | content_length = rtsp_get_content_length(line, linelen); |
1291 | |
|
1292 | 1 | } else if (HDR_MATCHES(rtsp_Session)) |
1293 | 0 | { |
1294 | 0 | session_id = tvb_format_text(pinfo->pool, tvb, value_offset, value_len); |
1295 | | /* Put the value into the protocol tree */ |
1296 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_session, tvb, |
1297 | 0 | offset, linelen, |
1298 | 0 | session_id); |
1299 | |
|
1300 | 1 | } else if (HDR_MATCHES(rtsp_X_Vig_Msisdn)) { |
1301 | | /* |
1302 | | * Extract the X_Vig_Msisdn string |
1303 | | */ |
1304 | 0 | if (colon_offset != -1) |
1305 | 0 | { |
1306 | | /* Put the value into the protocol tree */ |
1307 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_X_Vig_Msisdn,tvb, |
1308 | 0 | offset, linelen , |
1309 | 0 | tvb_format_text(pinfo->pool, tvb, value_offset, value_len)); |
1310 | 0 | sub_tree = proto_item_add_subtree(ti, ett_rtsp_method); |
1311 | |
|
1312 | 0 | e164_info.e164_number_type = CALLING_PARTY_NUMBER; |
1313 | 0 | e164_info.nature_of_address = 0; |
1314 | |
|
1315 | 0 | e164_info.E164_number_str = tvb_get_string_enc(pinfo->pool, tvb, value_offset, |
1316 | 0 | value_len, ENC_ASCII); |
1317 | 0 | e164_info.E164_number_length = value_len; |
1318 | 0 | dissect_e164_number(tvb, sub_tree, value_offset, |
1319 | 0 | value_len, e164_info); |
1320 | 0 | } |
1321 | 1 | } else if (HDR_MATCHES(rtsp_rdt_feature_level)) |
1322 | 0 | { |
1323 | 0 | bool rdt_feature_level_valid; |
1324 | 0 | rdt_feature_level_valid = ws_strtou32(tvb_format_text(pinfo->pool, tvb, value_offset, value_len), |
1325 | 0 | NULL, &rdt_feature_level); |
1326 | 0 | ti = proto_tree_add_uint(rtsp_tree, hf_rtsp_rdtfeaturelevel, |
1327 | 0 | tvb, offset, linelen, rdt_feature_level); |
1328 | 0 | if (!rdt_feature_level_valid) |
1329 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_rdtfeaturelevel_invalid); |
1330 | 1 | } else if (HDR_MATCHES(rtsp_cseq)) |
1331 | 0 | { |
1332 | 0 | cseq_valid = ws_strtou32(tvb_format_text(pinfo->pool, tvb, value_offset, value_len), |
1333 | 0 | NULL, &cseq); |
1334 | 0 | ti = proto_tree_add_uint(rtsp_tree, hf_rtsp_cseq, tvb, offset, linelen, cseq); |
1335 | 0 | if (!cseq_valid) { |
1336 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_cseq_invalid); |
1337 | 0 | } |
1338 | 1 | } else if (HDR_MATCHES(rtsp_content_base)) |
1339 | 0 | { |
1340 | 0 | content_base = (char *)tvb_get_string_enc(pinfo->pool, tvb, value_offset, value_len, ENC_UTF_8); |
1341 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_content_base, |
1342 | 0 | tvb, offset, linelen, content_base); |
1343 | 1 | } else if (HDR_MATCHES(rtsp_content_location)) |
1344 | 0 | { |
1345 | 0 | content_location = (char *)tvb_get_string_enc(pinfo->pool, tvb, value_offset, value_len, ENC_UTF_8); |
1346 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_content_location, |
1347 | 0 | tvb, offset, linelen, content_location); |
1348 | 0 | } |
1349 | 1 | else |
1350 | 1 | { |
1351 | | /* Default case for headers. Show line as text */ |
1352 | 1 | proto_tree_add_format_text(rtsp_tree, tvb, offset, next_offset - offset); |
1353 | 1 | } |
1354 | 1 | } |
1355 | 8 | else if (rtsp_type_line == RTSP_NOT_FIRST_LINE) |
1356 | 8 | { |
1357 | | /* Catch-all for all other lines... Show line as text. |
1358 | | TODO: should these be shown as errors? */ |
1359 | 8 | proto_tree_add_format_text(rtsp_tree, tvb, offset, next_offset - offset); |
1360 | 8 | } |
1361 | | |
1362 | 9 | offset = next_offset; |
1363 | 9 | } |
1364 | | |
1365 | 33 | if (cseq_valid) { |
1366 | 0 | rtsp_conversation_data_t *conv_data = get_rtsp_conversation_data(NULL, pinfo); |
1367 | 0 | rtsp_req_resp_t *curr_req_resp = wmem_map_lookup(conv_data->req_resp_map, GUINT_TO_POINTER(cseq)); |
1368 | 0 | if (!curr_req_resp) { |
1369 | 0 | curr_req_resp = wmem_new0(wmem_file_scope(), rtsp_req_resp_t); |
1370 | 0 | wmem_map_insert(conv_data->req_resp_map, GUINT_TO_POINTER(cseq), curr_req_resp); |
1371 | 0 | } |
1372 | 0 | if (rtsp_type_packet == RTSP_REQUEST) { |
1373 | 0 | if (curr_req_resp->req_frame == 0) { |
1374 | 0 | curr_req_resp->req_frame = pinfo->num; |
1375 | 0 | } |
1376 | 0 | if (curr_req_resp->resp_frame) { |
1377 | 0 | proto_tree_add_uint(req_tree, hf_rtsp_response_in, tvb, 0, 0, curr_req_resp->resp_frame); |
1378 | 0 | } |
1379 | 0 | if (request_uri && !curr_req_resp->request_uri) { |
1380 | 0 | curr_req_resp->request_uri = wmem_strdup(wmem_file_scope(), request_uri); |
1381 | 0 | } |
1382 | 0 | } else { |
1383 | 0 | if (curr_req_resp->resp_frame == 0) { |
1384 | 0 | curr_req_resp->resp_frame = pinfo->num; |
1385 | 0 | } |
1386 | 0 | if (curr_req_resp->request_uri) { |
1387 | 0 | ti = proto_tree_add_string(req_tree, hf_rtsp_url, tvb, 0, 0, curr_req_resp->request_uri); |
1388 | 0 | proto_item_set_generated(ti); |
1389 | 0 | } |
1390 | 0 | if (curr_req_resp->req_frame) { |
1391 | 0 | proto_tree_add_uint(req_tree, hf_rtsp_response_to, tvb, 0, 0, curr_req_resp->req_frame); |
1392 | 0 | } |
1393 | 0 | } |
1394 | 0 | if (curr_req_resp->request_uri) { |
1395 | 0 | base_uri = wmem_ascii_strdown(pinfo->pool, curr_req_resp->request_uri, -1); |
1396 | 0 | } |
1397 | 0 | } |
1398 | | |
1399 | 33 | if (content_base) { |
1400 | 0 | base_uri = content_base; |
1401 | 33 | } else if (content_location) { |
1402 | | /* XXX - Content-Location itself can be relative to the request_uri, at |
1403 | | * least according to RFC 7826. (RTSP 2.0 is not widely implemented, but |
1404 | | * it does have useful notes on gotchas and limitations of RTSP 1.0.) |
1405 | | */ |
1406 | 0 | base_uri = content_location; |
1407 | 0 | } |
1408 | | |
1409 | 33 | if (session_id) { |
1410 | 0 | stat_info = wmem_new0(pinfo->pool, voip_packet_info_t); |
1411 | 0 | stat_info->protocol_name = wmem_strdup(pinfo->pool, "RTSP"); |
1412 | 0 | stat_info->call_id = session_id; |
1413 | 0 | stat_info->frame_label = frame_label; |
1414 | 0 | stat_info->call_state = VOIP_CALL_SETUP; |
1415 | 0 | stat_info->call_active_state = VOIP_ACTIVE; |
1416 | 0 | stat_info->frame_comment = frame_label; |
1417 | 0 | tap_queue_packet(voip_tap, pinfo, stat_info); |
1418 | 0 | } |
1419 | | |
1420 | 33 | if (transport_line) { |
1421 | | /* |
1422 | | * Based on the port numbers specified in the Transport: header, set up |
1423 | | * a conversation that will be dissected with the appropriate dissector. |
1424 | | */ |
1425 | 0 | setup_info = rtsp_create_setup_info(pinfo, session_id, base_uri); |
1426 | 0 | rtsp_create_conversation(pinfo, ti, transport_line, transport_linelen, rdt_feature_level, rtsp_type_packet, setup_info); |
1427 | 0 | } |
1428 | | |
1429 | | /* |
1430 | | * Have now read all of the lines of this message. |
1431 | | * |
1432 | | * If a content length was supplied, the amount of data to be |
1433 | | * processed as RTSP payload is the minimum of the content |
1434 | | * length and the amount of data remaining in the frame. |
1435 | | * |
1436 | | * If no content length was supplied (or if a bad content length |
1437 | | * was supplied), the amount of data to be processed is the amount |
1438 | | * of data remaining in the frame. |
1439 | | */ |
1440 | 33 | datalen = tvb_captured_length_remaining(tvb, offset); |
1441 | 33 | reported_datalen = tvb_reported_length_remaining(tvb, offset); |
1442 | 33 | if (content_length != -1) { |
1443 | | /* |
1444 | | * Content length specified; display only that amount |
1445 | | * as payload. |
1446 | | */ |
1447 | 0 | if (datalen > content_length) |
1448 | 0 | datalen = content_length; |
1449 | | |
1450 | | /* |
1451 | | * XXX - limit the reported length in the tvbuff we'll |
1452 | | * hand to a subdissector to be no greater than the |
1453 | | * content length. |
1454 | | * |
1455 | | * We really need both unreassembled and "how long it'd |
1456 | | * be if it were reassembled" lengths for tvbuffs, so |
1457 | | * that we throw the appropriate exceptions for |
1458 | | * "not enough data captured" (running past the length), |
1459 | | * "packet needed reassembly" (within the length but |
1460 | | * running past the unreassembled length), and |
1461 | | * "packet is malformed" (running past the reassembled |
1462 | | * length). |
1463 | | */ |
1464 | 0 | if (reported_datalen > content_length) |
1465 | 0 | reported_datalen = content_length; |
1466 | 33 | } else { |
1467 | | /* |
1468 | | * No content length specified; if this message doesn't |
1469 | | * have a body if no content length is specified, process |
1470 | | * nothing as payload. |
1471 | | */ |
1472 | 33 | if (body_requires_content_len) |
1473 | 0 | datalen = 0; |
1474 | 33 | } |
1475 | | |
1476 | 33 | if (datalen > 0) { |
1477 | | /* |
1478 | | * There's stuff left over; process it. |
1479 | | */ |
1480 | 30 | tvbuff_t *new_tvb; |
1481 | | |
1482 | | /* |
1483 | | * Now create a tvbuff for the Content-type stuff and |
1484 | | * dissect it. |
1485 | | * |
1486 | | * The amount of data to be processed that's |
1487 | | * available in the tvbuff is "datalen", which |
1488 | | * is the minimum of the amount of data left in |
1489 | | * the tvbuff and any specified content length. |
1490 | | * |
1491 | | * The amount of data to be processed that's in |
1492 | | * this frame, regardless of whether it was |
1493 | | * captured or not, is "reported_datalen", |
1494 | | * which, if no content length was specified, |
1495 | | * is -1, i.e. "to the end of the frame. |
1496 | | */ |
1497 | 30 | new_tvb = tvb_new_subset_length_caplen(tvb, offset, datalen, |
1498 | 30 | reported_datalen); |
1499 | | |
1500 | | /* |
1501 | | * Check if next line is RTSP message - pipelining |
1502 | | * If yes, stop processing and start next loop |
1503 | | * If no, process rest of packet with dissectors |
1504 | | */ |
1505 | 30 | first_linelen = tvb_find_line_end(new_tvb, 0, -1, &next_offset, false); |
1506 | 30 | line = tvb_get_ptr(new_tvb, 0, first_linelen); |
1507 | 30 | is_request_or_reply = is_rtsp_request_or_reply(line, first_linelen, |
1508 | 30 | &rtsp_type_packet, rtsp_stat_info, pinfo->pool); |
1509 | | |
1510 | 30 | if (!is_request_or_reply){ |
1511 | 30 | setup_info = rtsp_create_setup_info(pinfo, session_id, base_uri); |
1512 | 30 | media_content_info_t content_info = { MEDIA_CONTAINER_SIP_DATA, media_type_str_lower_case, NULL, setup_info }; |
1513 | 30 | if (media_type_str_lower_case && |
1514 | 30 | dissector_try_string_with_data(media_type_dissector_table, |
1515 | 0 | media_type_str_lower_case, |
1516 | 0 | new_tvb, pinfo, rtsp_tree, true, &content_info)) { |
1517 | |
|
1518 | 30 | } else { |
1519 | | /* |
1520 | | * Fix up the top-level item so that it doesn't |
1521 | | * include the SDP stuff. |
1522 | | */ |
1523 | 30 | if (ti_top != NULL) |
1524 | 30 | proto_item_set_len(ti_top, offset); |
1525 | | |
1526 | 30 | if (tvb_get_uint8(tvb, offset) == RTSP_FRAMEHDR) { |
1527 | | /* |
1528 | | * This is interleaved stuff; don't |
1529 | | * treat it as raw data - set "datalen" |
1530 | | * to 0, so we won't skip the offset |
1531 | | * past it, which will cause our |
1532 | | * caller to process that stuff itself. |
1533 | | */ |
1534 | 0 | datalen = 0; |
1535 | 30 | } else { |
1536 | 30 | proto_tree_add_bytes_format(rtsp_tree, hf_rtsp_data, tvb, offset, |
1537 | 30 | datalen, NULL, "Data (%d bytes)", |
1538 | 30 | reported_datalen); |
1539 | 30 | } |
1540 | 30 | } |
1541 | | |
1542 | | /* |
1543 | | * We've processed "datalen" bytes worth of data |
1544 | | * (which may be no data at all); advance the |
1545 | | * offset past whatever data we've processed. |
1546 | | */ |
1547 | 30 | offset += datalen; |
1548 | 30 | } |
1549 | 30 | } |
1550 | | |
1551 | 33 | tap_queue_packet(rtsp_tap, pinfo, rtsp_stat_info); |
1552 | | |
1553 | 33 | return offset - orig_offset; |
1554 | 37 | } |
1555 | | |
1556 | | static char* |
1557 | | process_rtsp_request(tvbuff_t *tvb, int offset, const unsigned char *data, |
1558 | | size_t linelen, packet_info *pinfo, proto_tree *tree) |
1559 | 0 | { |
1560 | 0 | const unsigned char *lineend = data + linelen; |
1561 | 0 | unsigned ii; |
1562 | 0 | const unsigned char *url; |
1563 | 0 | const unsigned char *url_start; |
1564 | 0 | unsigned char *tmp_url; |
1565 | | |
1566 | | /* Request Methods */ |
1567 | 0 | for (ii = 0; ii < RTSP_NMETHODS; ii++) { |
1568 | 0 | size_t len = strlen(rtsp_methods[ii]); |
1569 | 0 | if (linelen >= len && |
1570 | 0 | g_ascii_strncasecmp(rtsp_methods[ii], data, len) == 0 && |
1571 | 0 | (len == linelen || g_ascii_isspace(data[len]))) |
1572 | 0 | break; |
1573 | 0 | } |
1574 | 0 | if (ii == RTSP_NMETHODS) { |
1575 | | /* |
1576 | | * We got here because "is_rtsp_request_or_reply()" returned |
1577 | | * RTSP_REQUEST, so we know one of the request methods |
1578 | | * matched, so we "can't get here". |
1579 | | */ |
1580 | 0 | DISSECTOR_ASSERT_NOT_REACHED(); |
1581 | 0 | } |
1582 | | |
1583 | | /* Add method name to tree */ |
1584 | 0 | proto_tree_add_string(tree, hf_rtsp_method, tvb, offset, |
1585 | 0 | (int) strlen(rtsp_methods[ii]), rtsp_methods[ii]); |
1586 | | |
1587 | | /* URL */ |
1588 | 0 | url = data; |
1589 | | /* Skip method name again */ |
1590 | 0 | while (url < lineend && !g_ascii_isspace(*url)) |
1591 | 0 | url++; |
1592 | | /* Skip spaces */ |
1593 | 0 | while (url < lineend && g_ascii_isspace(*url)) |
1594 | 0 | url++; |
1595 | | /* URL starts here */ |
1596 | 0 | url_start = url; |
1597 | | /* Scan to end of URL */ |
1598 | 0 | while (url < lineend && !g_ascii_isspace(*url)) |
1599 | 0 | url++; |
1600 | | /* Create a URL-sized buffer and copy contents */ |
1601 | 0 | tmp_url = format_text(pinfo->pool, url_start, url - url_start); |
1602 | | |
1603 | | /* Add URL to tree */ |
1604 | 0 | proto_tree_add_string(tree, hf_rtsp_url, tvb, |
1605 | 0 | offset + (int) (url_start - data), (int) (url - url_start), tmp_url); |
1606 | 0 | return tmp_url; |
1607 | 0 | } |
1608 | | |
1609 | | /* Read first line of a reply message */ |
1610 | | static void |
1611 | | process_rtsp_reply(tvbuff_t *tvb, int offset, const unsigned char *data, |
1612 | | size_t linelen, packet_info *pinfo _U_, proto_tree *tree) |
1613 | 0 | { |
1614 | 0 | const unsigned char *lineend = data + linelen; |
1615 | 0 | const unsigned char *status = data; |
1616 | 0 | const unsigned char *status_start; |
1617 | 0 | unsigned status_i; |
1618 | | |
1619 | | /* status code */ |
1620 | | |
1621 | | /* Skip protocol/version */ |
1622 | 0 | while (status < lineend && !g_ascii_isspace(*status)) |
1623 | 0 | status++; |
1624 | | /* Skip spaces */ |
1625 | 0 | while (status < lineend && g_ascii_isspace(*status)) |
1626 | 0 | status++; |
1627 | | |
1628 | | /* Actual code number now */ |
1629 | 0 | status_start = status; |
1630 | 0 | status_i = 0; |
1631 | 0 | while (status < lineend && g_ascii_isdigit(*status)) |
1632 | 0 | status_i = status_i * 10 + *status++ - '0'; |
1633 | | |
1634 | | /* Add field to tree */ |
1635 | 0 | proto_tree_add_uint(tree, hf_rtsp_status, tvb, |
1636 | 0 | offset + (int) (status_start - data), |
1637 | 0 | (int) (status - status_start), status_i); |
1638 | 0 | } |
1639 | | |
1640 | | static int |
1641 | | dissect_rtsp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
1642 | 39 | { |
1643 | 39 | int offset = 0; |
1644 | 39 | int len; |
1645 | | |
1646 | 79 | while (tvb_reported_length_remaining(tvb, offset) != 0) { |
1647 | | /* |
1648 | | * Add separator between multiple messages in column info text |
1649 | | */ |
1650 | 40 | if (offset > 0) { |
1651 | 1 | col_set_str(pinfo->cinfo, COL_INFO, ", "); |
1652 | 1 | col_set_fence(pinfo->cinfo, COL_INFO); |
1653 | 1 | } |
1654 | 40 | len = (tvb_get_uint8(tvb, offset) == RTSP_FRAMEHDR) |
1655 | 40 | ? dissect_rtspinterleaved(tvb, offset, pinfo, tree) |
1656 | 40 | : dissect_rtspmessage(tvb, offset, pinfo, tree); |
1657 | 40 | if (len == -1) |
1658 | 0 | break; |
1659 | 40 | offset += len; |
1660 | | |
1661 | | /* |
1662 | | * OK, we've set the Protocol and Info columns for the |
1663 | | * first RTSP message; set fence so changes are kept for |
1664 | | * subsequent RTSP messages. |
1665 | | */ |
1666 | 40 | col_set_fence(pinfo->cinfo, COL_INFO); |
1667 | 40 | } |
1668 | 39 | return tvb_captured_length(tvb); |
1669 | 39 | } |
1670 | | |
1671 | | void |
1672 | | proto_register_rtsp(void) |
1673 | 14 | { |
1674 | 14 | static int *ett[] = { |
1675 | 14 | &ett_rtspframe, |
1676 | 14 | &ett_rtsp, |
1677 | 14 | &ett_rtsp_method, |
1678 | 14 | }; |
1679 | 14 | static hf_register_info hf[] = { |
1680 | 14 | { &hf_rtsp_request, |
1681 | 14 | { "Request", "rtsp.request", FT_STRING, BASE_NONE, NULL, 0, |
1682 | 14 | NULL, HFILL }}, |
1683 | 14 | { &hf_rtsp_response, |
1684 | 14 | { "Response", "rtsp.response", FT_STRING, BASE_NONE, NULL, 0, |
1685 | 14 | NULL, HFILL }}, |
1686 | 14 | { &hf_rtsp_response_in, |
1687 | 14 | { "Response in frame", "rtsp.response_in", FT_FRAMENUM, BASE_NONE, |
1688 | 14 | FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0, NULL, HFILL }}, |
1689 | 14 | { &hf_rtsp_response_to, |
1690 | 14 | { "Response to frame", "rtsp.response_to", FT_FRAMENUM, BASE_NONE, |
1691 | 14 | FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0, NULL, HFILL }}, |
1692 | 14 | { &hf_rtsp_method, |
1693 | 14 | { "Method", "rtsp.method", FT_STRING, BASE_NONE, NULL, 0, |
1694 | 14 | NULL, HFILL }}, |
1695 | 14 | { &hf_rtsp_content_type, |
1696 | 14 | { "Content-type", "rtsp.content-type", FT_STRING, BASE_NONE, NULL, 0, |
1697 | 14 | NULL, HFILL }}, |
1698 | 14 | { &hf_rtsp_content_length, |
1699 | 14 | { "Content-length", "rtsp.content-length", FT_UINT32, BASE_DEC, NULL, 0, |
1700 | 14 | NULL, HFILL }}, |
1701 | 14 | { &hf_rtsp_url, |
1702 | 14 | { "URL", "rtsp.url", FT_STRING, BASE_NONE, NULL, 0, |
1703 | 14 | NULL, HFILL }}, |
1704 | 14 | { &hf_rtsp_status, |
1705 | 14 | { "Status", "rtsp.status", FT_UINT32, BASE_DEC, NULL, 0, |
1706 | 14 | NULL, HFILL }}, |
1707 | 14 | { &hf_rtsp_session, |
1708 | 14 | { "Session", "rtsp.session", FT_STRING, BASE_NONE, NULL, 0, |
1709 | 14 | NULL, HFILL }}, |
1710 | 14 | { &hf_rtsp_transport, |
1711 | 14 | { "Transport", "rtsp.transport", FT_STRING, BASE_NONE, NULL, 0, |
1712 | 14 | NULL, HFILL }}, |
1713 | 14 | { &hf_rtsp_rdtfeaturelevel, |
1714 | 14 | { "RDTFeatureLevel", "rtsp.rdt-feature-level", FT_UINT32, BASE_DEC, NULL, 0, |
1715 | 14 | NULL, HFILL }}, |
1716 | 14 | { &hf_rtsp_cseq, |
1717 | 14 | { "CSeq", "rtsp.cseq", FT_UINT32, BASE_DEC, NULL, 0, |
1718 | 14 | NULL, HFILL }}, |
1719 | 14 | { &hf_rtsp_content_base, |
1720 | 14 | { "Content-Base", "rtsp.content-base", FT_STRING, BASE_NONE, NULL, 0, |
1721 | 14 | NULL, HFILL }}, |
1722 | 14 | { &hf_rtsp_content_location, |
1723 | 14 | { "Content-Location", "rtsp.content-location", FT_STRING, BASE_NONE, NULL, 0, |
1724 | 14 | NULL, HFILL }}, |
1725 | 14 | { &hf_rtsp_X_Vig_Msisdn, |
1726 | 14 | { "X-Vig-Msisdn", "rtsp.X_Vig_Msisdn", FT_STRING, BASE_NONE, NULL, 0, |
1727 | 14 | NULL, HFILL }}, |
1728 | 14 | { &hf_rtsp_magic, |
1729 | 14 | { "Magic", "rtsp.magic", FT_UINT8, BASE_HEX, NULL, 0x0, |
1730 | 14 | NULL, HFILL }}, |
1731 | 14 | { &hf_rtsp_channel, |
1732 | 14 | { "Channel", "rtsp.channel", FT_UINT8, BASE_HEX, NULL, 0x0, |
1733 | 14 | NULL, HFILL }}, |
1734 | 14 | { &hf_rtsp_length, |
1735 | 14 | { "Length", "rtsp.length", FT_UINT16, BASE_DEC, NULL, 0x0, |
1736 | 14 | NULL, HFILL }}, |
1737 | 14 | { &hf_rtsp_data, |
1738 | 14 | { "Data", "rtsp.data", FT_BYTES, BASE_NONE, NULL, 0x0, |
1739 | 14 | NULL, HFILL }}, |
1740 | 14 | }; |
1741 | | |
1742 | 14 | static ei_register_info ei[] = { |
1743 | 14 | { &ei_rtsp_unknown_transport_type, |
1744 | 14 | { "rtsp.unknown_transport_type", PI_UNDECODED, PI_WARN, "Unknown transport type", EXPFILL }}, |
1745 | 14 | { &ei_rtsp_bad_server_port, |
1746 | 14 | { "rtsp.bad_server_port", PI_UNDECODED, PI_WARN, "Bad server_port", EXPFILL }}, |
1747 | 14 | { &ei_rtsp_bad_client_port, |
1748 | 14 | { "rtsp.bad_client_port", PI_UNDECODED, PI_WARN, "Bad client port", EXPFILL }}, |
1749 | 14 | { &ei_rtsp_bad_interleaved_channel, |
1750 | 14 | { "rtsp.bad_interleaved_channel", PI_UNDECODED, PI_WARN, "Bad interleaved_channel", EXPFILL }}, |
1751 | 14 | { &ei_rtsp_content_length_invalid, |
1752 | 14 | { "rtsp.content-length.invalid", PI_MALFORMED, PI_ERROR, "Invalid content length", EXPFILL }}, |
1753 | 14 | { &ei_rtsp_rdtfeaturelevel_invalid, |
1754 | 14 | { "rtsp.rdt-feature-level.invalid", PI_MALFORMED, PI_ERROR, "Invalid RDTFeatureLevel", EXPFILL }}, |
1755 | 14 | { &ei_rtsp_cseq_invalid, |
1756 | 14 | { "rtsp.cseq.invalid", PI_PROTOCOL, PI_WARN, "Invalid CSeq", EXPFILL }}, |
1757 | 14 | { &ei_rtsp_bad_server_ip_address, |
1758 | 14 | { "rtsp.bad_server_ip_address", PI_MALFORMED, PI_ERROR, "Bad server IP address", EXPFILL }}, |
1759 | 14 | { &ei_rtsp_bad_client_ip_address, |
1760 | 14 | { "rtsp.bad_client_ip_address", PI_MALFORMED, PI_ERROR, "Bad client IP address", EXPFILL }} |
1761 | 14 | }; |
1762 | | |
1763 | 14 | module_t *rtsp_module; |
1764 | 14 | expert_module_t *expert_rtsp; |
1765 | | |
1766 | 14 | proto_rtsp = proto_register_protocol("Real Time Streaming Protocol", "RTSP", "rtsp"); |
1767 | | |
1768 | 14 | proto_register_field_array(proto_rtsp, hf, array_length(hf)); |
1769 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
1770 | | |
1771 | 14 | expert_rtsp = expert_register_protocol(proto_rtsp); |
1772 | 14 | expert_register_field_array(expert_rtsp, ei, array_length(ei)); |
1773 | | |
1774 | | /* Make this dissector findable by name */ |
1775 | 14 | rtsp_handle = register_dissector("rtsp", dissect_rtsp, proto_rtsp); |
1776 | | |
1777 | | /* Register our configuration options, particularly our ports */ |
1778 | | |
1779 | 14 | rtsp_module = prefs_register_protocol(proto_rtsp, NULL); |
1780 | | |
1781 | 14 | prefs_register_obsolete_preference(rtsp_module, "tcp.alternate_port"); |
1782 | | |
1783 | 14 | prefs_register_bool_preference(rtsp_module, "desegment_headers", |
1784 | 14 | "Reassemble RTSP headers spanning multiple TCP segments", |
1785 | 14 | "Whether the RTSP dissector should reassemble headers " |
1786 | 14 | "of a request spanning multiple TCP segments. " |
1787 | 14 | " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", |
1788 | 14 | &rtsp_desegment_headers); |
1789 | 14 | prefs_register_bool_preference(rtsp_module, "desegment_body", |
1790 | 14 | "Trust the \"Content-length:\" header when desegmenting", |
1791 | 14 | "Whether the RTSP dissector should use the " |
1792 | 14 | "\"Content-length:\" value to desegment the body " |
1793 | 14 | "of a request spanning multiple TCP segments", |
1794 | 14 | &rtsp_desegment_body); |
1795 | | |
1796 | | /* |
1797 | | * Heuristic dissectors SHOULD register themselves in |
1798 | | * this table using the standard heur_dissector_add() |
1799 | | * function. |
1800 | | */ |
1801 | 14 | heur_subdissector_list = register_heur_dissector_list_with_description("rtsp", "RTSP data", proto_rtsp); |
1802 | | |
1803 | | /* |
1804 | | * Register for tapping |
1805 | | */ |
1806 | 14 | rtsp_tap = register_tap("rtsp"); /* RTSP statistics tap */ |
1807 | 14 | } |
1808 | | |
1809 | | void |
1810 | | proto_reg_handoff_rtsp(void) |
1811 | 14 | { |
1812 | 14 | rtp_handle = find_dissector_add_dependency("rtp", proto_rtsp); |
1813 | 14 | rtp_rfc4571_handle = find_dissector_add_dependency("rtp.rfc4571", proto_rtsp); |
1814 | 14 | rtcp_handle = find_dissector_add_dependency("rtcp", proto_rtsp); |
1815 | 14 | rdt_handle = find_dissector_add_dependency("rdt", proto_rtsp); |
1816 | 14 | media_type_dissector_table = find_dissector_table("media_type"); |
1817 | 14 | voip_tap = find_tap_id("voip"); |
1818 | | |
1819 | | /* Set our port number for future use */ |
1820 | 14 | dissector_add_uint_range_with_preference("tcp.port", RTSP_TCP_PORT_RANGE, rtsp_handle); |
1821 | | |
1822 | | /* XXX: Do the following only once ?? */ |
1823 | 14 | stats_tree_register("rtsp","rtsp","RTSP" STATS_TREE_MENU_SEPARATOR "Packet Counter", 0, rtsp_stats_tree_packet, rtsp_stats_tree_init, NULL ); |
1824 | | |
1825 | 14 | } |
1826 | | |
1827 | | /* |
1828 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
1829 | | * |
1830 | | * Local variables: |
1831 | | * c-basic-offset: 4 |
1832 | | * tab-width: 8 |
1833 | | * indent-tabs-mode: space |
1834 | | * End: |
1835 | | * |
1836 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
1837 | | * :indentSize=4:tabSize=8:noTabs=true: |
1838 | | */ |