/src/wireshark/epan/dissectors/packet-rtsp.c
Line | Count | Source |
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 | | static 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 | char *str; |
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 | str = wmem_strdup_printf(pinfo->pool, "%u %s", i, val_to_str(pinfo->pool, 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 | 16 | #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 | 441 | #define STRLEN_CONST(str) (sizeof (str) - 1) |
267 | | |
268 | 79 | #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, unsigned offset, packet_info *pinfo, |
321 | | proto_tree *tree) |
322 | 1 | { |
323 | 1 | unsigned length_remaining; |
324 | 1 | proto_item *ti; |
325 | 1 | proto_tree *rtspframe_tree = NULL; |
326 | 1 | unsigned orig_offset; |
327 | 1 | uint8_t rf_chan; /* interleaved channel id */ |
328 | 1 | uint16_t rf_len; /* packet length */ |
329 | 1 | tvbuff_t *next_tvb; |
330 | 1 | conversation_t *conv; |
331 | 1 | rtsp_conversation_data_t *data; |
332 | 1 | 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 | 1 | length_remaining = tvb_ensure_captured_length_remaining(tvb, offset); |
340 | | |
341 | | /* |
342 | | * Can we do reassembly? |
343 | | */ |
344 | 1 | 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 | 1 | orig_offset = offset; |
368 | 1 | rf_chan = tvb_get_uint8(tvb, offset+1); |
369 | 1 | rf_len = tvb_get_ntohs(tvb, offset+2); |
370 | | |
371 | | /* |
372 | | * Can we do reassembly? |
373 | | */ |
374 | 1 | 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 | 1 | col_add_fstr(pinfo->cinfo, COL_INFO, |
392 | 1 | "Interleaved channel 0x%02x, %u bytes", |
393 | 1 | rf_chan, rf_len); |
394 | | |
395 | 1 | ti = proto_tree_add_protocol_format(tree, proto_rtsp, tvb, |
396 | 1 | offset, 4, |
397 | 1 | "RTSP Interleaved Frame, Channel: 0x%02x, %u bytes", |
398 | 1 | rf_chan, rf_len); |
399 | 1 | rtspframe_tree = proto_item_add_subtree(ti, ett_rtspframe); |
400 | | |
401 | 1 | proto_tree_add_item(rtspframe_tree, hf_rtsp_magic, tvb, offset, 1, ENC_BIG_ENDIAN); |
402 | | |
403 | 1 | offset += 1; |
404 | | |
405 | 1 | proto_tree_add_item(rtspframe_tree, hf_rtsp_channel, tvb, offset, 1, ENC_BIG_ENDIAN); |
406 | | |
407 | 1 | offset += 1; |
408 | | |
409 | 1 | proto_tree_add_item(rtspframe_tree, hf_rtsp_length, tvb, offset, 2, ENC_BIG_ENDIAN); |
410 | 1 | offset += 2; |
411 | | |
412 | 1 | next_tvb = tvb_new_subset_length(tvb, offset, rf_len); |
413 | | |
414 | 1 | conv = find_conversation_pinfo_strat(pinfo, 0); |
415 | | |
416 | 1 | if (conv && |
417 | 1 | (data = (rtsp_conversation_data_t *)conversation_get_proto_data(conv, proto_rtsp)) && |
418 | | /* Add the following condition if it is not always true. |
419 | | rf_chan < RTSP_MAX_INTERLEAVED && |
420 | | */ |
421 | 0 | (dissector = data->interleaved[rf_chan].dissector)) { |
422 | 0 | call_dissector(dissector, next_tvb, pinfo, tree); |
423 | 1 | } else { |
424 | 1 | bool dissected = false; |
425 | 1 | heur_dtbl_entry_t *hdtbl_entry = NULL; |
426 | | |
427 | 1 | dissected = dissector_try_heuristic(heur_subdissector_list, |
428 | 1 | next_tvb, pinfo, tree, &hdtbl_entry, NULL); |
429 | | |
430 | 1 | if (!dissected) { |
431 | 1 | proto_tree_add_item(rtspframe_tree, hf_rtsp_data, tvb, offset, rf_len, ENC_NA); |
432 | 1 | } |
433 | 1 | } |
434 | | |
435 | 1 | offset += rf_len; |
436 | | |
437 | 1 | return offset - orig_offset; |
438 | 1 | } |
439 | | |
440 | | static char* process_rtsp_request(tvbuff_t *tvb, |
441 | | unsigned linelen, packet_info *pinfo, proto_tree *tree); |
442 | | |
443 | | static void process_rtsp_reply(tvbuff_t *tvb, unsigned linelen, packet_info *pinfo, proto_tree *tree); |
444 | | |
445 | | typedef enum { |
446 | | RTSP_REQUEST, |
447 | | RTSP_REPLY, |
448 | | RTSP_NOT_FIRST_LINE |
449 | | } rtsp_type_t; |
450 | | |
451 | | static const char *rtsp_methods[] = { |
452 | | "DESCRIBE", |
453 | | "ANNOUNCE", |
454 | | "GET_PARAMETER", |
455 | | "OPTIONS", |
456 | | "PAUSE", |
457 | | "PLAY", |
458 | | "RECORD", |
459 | | "REDIRECT", |
460 | | "SETUP", |
461 | | "SET_PARAMETER", |
462 | | "TEARDOWN" |
463 | | }; |
464 | | |
465 | 1.60k | #define RTSP_NMETHODS array_length(rtsp_methods) |
466 | | |
467 | | static bool |
468 | | is_rtsp_request_or_reply(tvbuff_t *tvb, unsigned linelen, rtsp_type_t *type, |
469 | | rtsp_info_value_t *rtsp_stat_info, wmem_allocator_t *pool) |
470 | 134 | { |
471 | 134 | unsigned ii; |
472 | 134 | unsigned offset = 0, next_offset; |
473 | 134 | unsigned tokenlen; |
474 | | |
475 | 134 | tvb_get_token_len_length(tvb, offset, linelen, &tokenlen, &next_offset); |
476 | | |
477 | | /* Is this an RTSP reply? */ |
478 | 134 | if (linelen >= 5 && tvb_strncaseeql(tvb, offset, "RTSP/", 5) == 0) { |
479 | | /* |
480 | | * Yes. |
481 | | */ |
482 | 0 | *type = RTSP_REPLY; |
483 | | /* The first token is the version. */ |
484 | 0 | if (tvb_reported_length_remaining(tvb, next_offset)) { |
485 | | /* The next token is the status code. */ |
486 | 0 | offset = next_offset; |
487 | 0 | tvb_get_token_len_length(tvb, offset, tvb_reported_length_remaining(tvb, offset), &tokenlen, NULL); |
488 | 0 | if (tokenlen >= 3) { |
489 | 0 | tvb_get_string_uint(tvb, offset, tokenlen, ENC_STR_DEC, &rtsp_stat_info->response_code, NULL); |
490 | 0 | } |
491 | 0 | } |
492 | 0 | return true; |
493 | 0 | } |
494 | | |
495 | | /* |
496 | | * Is this an RTSP request? |
497 | | * Check whether the line begins with one of the RTSP request |
498 | | * methods. |
499 | | */ |
500 | 1.60k | for (ii = 0; ii < RTSP_NMETHODS; ii++) { |
501 | 1.47k | size_t len = strlen(rtsp_methods[ii]); |
502 | 1.47k | if (len == (size_t)tokenlen && |
503 | 38 | tvb_strncaseeql(tvb, offset, rtsp_methods[ii], len) == 0) |
504 | 0 | { |
505 | 0 | *type = RTSP_REQUEST; |
506 | 0 | rtsp_stat_info->request_method = |
507 | 0 | wmem_strndup(pool, rtsp_methods[ii], len+1); |
508 | 0 | return true; |
509 | 0 | } |
510 | 1.47k | } |
511 | | |
512 | | /* Wasn't a request or a response */ |
513 | 134 | *type = RTSP_NOT_FIRST_LINE; |
514 | 134 | return false; |
515 | 134 | } |
516 | | |
517 | | static const char rtsp_content_type[] = "Content-Type:"; |
518 | | static const char rtsp_transport[] = "Transport:"; |
519 | | static const char rtsp_sps_server_port[] = "server_port="; |
520 | | static const char rtsp_cps_server_port[] = "client_port="; |
521 | | static const char rtsp_sps_dest_addr[] = "dest_addr="; |
522 | | static const char rtsp_cps_src_addr[] = "src_addr="; |
523 | | static const char rtsp_rtp_udp_default[] = "rtp/avp"; |
524 | | static const char rtsp_rtp_udp[] = "rtp/avp/udp"; |
525 | | static const char rtsp_rtp_tcp[] = "rtp/avp/tcp"; |
526 | | static const char rtsp_rdt_feature_level[] = "RDTFeatureLevel"; |
527 | | static const char rtsp_real_rdt[] = "x-real-rdt/"; |
528 | | static const char rtsp_real_tng[] = "x-pn-tng/"; /* synonym for x-real-rdt */ |
529 | | static const char rtsp_inter[] = "interleaved="; |
530 | | static const char rtsp_cseq[] = "CSeq:"; |
531 | | static const char rtsp_content_base[] = "Content-Base:"; |
532 | | static const char rtsp_content_location[] = "Content-Location:"; |
533 | | |
534 | | static sdp_setup_info_t* |
535 | | rtsp_create_setup_info(packet_info *pinfo, const char* session_id, const char *base_uri) |
536 | 30 | { |
537 | 30 | sdp_setup_info_t *setup_info = NULL; |
538 | 30 | if (!PINFO_FD_VISITED(pinfo)) { |
539 | | // setup_info is only used on the first pass (by SDP or RTP) |
540 | 30 | setup_info = wmem_new0(pinfo->pool, sdp_setup_info_t); |
541 | 30 | setup_info->hf_id = hf_rtsp_session; |
542 | 30 | setup_info->hf_type = SDP_TRACE_ID_HF_TYPE_STR; |
543 | | /* The session is a mandatory, opaque string for the session - but |
544 | | * not necessarily available at the time of media initialization via SDP, |
545 | | * whether DESCRIBE or via HTTP or some other protocol. It is known at |
546 | | * the time of actual RTP setup by RTSP, though. |
547 | | * |
548 | | * wmem_strdup will return "<NULL>" when it is not available, which |
549 | | * shouldn't ever be used by SDP (due to the "control" media attribute) |
550 | | * but prevents a possible null dereference with, e.g., fuzzed captures. |
551 | | * |
552 | | * It's in file scope (unlike the setup_info struct itself) because the |
553 | | * SDP and RTP dissectors don't copy the string but use it directly. |
554 | | * We probably could store the session id copy in conversation data. |
555 | | */ |
556 | 30 | setup_info->trace_id.str = wmem_strdup(wmem_file_scope(), session_id); |
557 | 30 | setup_info->base_uri = base_uri; |
558 | 30 | } |
559 | | |
560 | 30 | return setup_info; |
561 | 30 | } |
562 | | |
563 | | static void |
564 | | rtsp_create_conversation(packet_info *pinfo, proto_item *ti, |
565 | | const char *line_begin, size_t line_len, |
566 | | uint32_t rdt_feature_level, |
567 | | rtsp_type_t rtsp_type_packet, |
568 | | sdp_setup_info_t *setup_info) |
569 | 0 | { |
570 | 0 | char buf[256]; |
571 | 0 | char *tmp; |
572 | 0 | bool rtp_udp_transport = false; |
573 | 0 | bool rtp_tcp_transport = false; |
574 | 0 | bool rdt_transport = false; |
575 | 0 | unsigned c_data_port, c_mon_port; |
576 | 0 | unsigned s_data_port, s_mon_port; |
577 | 0 | unsigned ipv4_1, ipv4_2, ipv4_3, ipv4_4; |
578 | 0 | bool is_video = false; /* FIX ME - need to indicate video or not */ |
579 | 0 | address src_addr; |
580 | 0 | address dst_addr; |
581 | 0 | uint32_t ip4_addr; |
582 | 0 | rtp_dyn_payload_t *rtp_dyn_payload = NULL; |
583 | |
|
584 | 0 | if (rtsp_type_packet != RTSP_REPLY) { |
585 | 0 | return; |
586 | 0 | } |
587 | | |
588 | 0 | src_addr=pinfo->src; |
589 | 0 | dst_addr=pinfo->dst; |
590 | | |
591 | | /* Copy line into buf */ |
592 | 0 | if (line_len > sizeof(buf) - 1) |
593 | 0 | { |
594 | | /* Don't overflow the buffer. */ |
595 | 0 | line_len = sizeof(buf) - 1; |
596 | 0 | } |
597 | 0 | memcpy(buf, line_begin, line_len); |
598 | 0 | buf[line_len] = '\0'; |
599 | | |
600 | | /* Get past "Transport:" and spaces */ |
601 | 0 | tmp = buf + STRLEN_CONST(rtsp_transport); |
602 | 0 | while (*tmp && g_ascii_isspace(*tmp)) |
603 | 0 | tmp++; |
604 | | |
605 | | /* Work out which transport type is here */ |
606 | 0 | if (g_ascii_strncasecmp(tmp, rtsp_rtp_udp, strlen(rtsp_rtp_udp)) == 0) |
607 | 0 | { |
608 | 0 | rtp_udp_transport = true; |
609 | 0 | } |
610 | 0 | else if (g_ascii_strncasecmp(tmp, rtsp_rtp_tcp, strlen(rtsp_rtp_tcp)) == 0) |
611 | 0 | { |
612 | 0 | rtp_tcp_transport = true; |
613 | 0 | } |
614 | 0 | else if (g_ascii_strncasecmp(tmp, rtsp_rtp_udp_default, strlen(rtsp_rtp_udp_default)) == 0) |
615 | 0 | { |
616 | 0 | rtp_udp_transport = true; |
617 | 0 | } |
618 | 0 | else if (g_ascii_strncasecmp(tmp, rtsp_real_rdt, strlen(rtsp_real_rdt)) == 0 || |
619 | 0 | g_ascii_strncasecmp(tmp, rtsp_real_tng, strlen(rtsp_real_tng)) == 0) |
620 | 0 | { |
621 | 0 | rdt_transport = true; |
622 | 0 | } |
623 | 0 | else |
624 | 0 | { |
625 | | /* Give up on unknown transport types */ |
626 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_unknown_transport_type); |
627 | 0 | return; |
628 | 0 | } |
629 | | |
630 | 0 | c_data_port = c_mon_port = 0; |
631 | 0 | s_data_port = s_mon_port = 0; |
632 | | |
633 | | /* Look for server port */ |
634 | 0 | if ((tmp = strstr(buf, rtsp_sps_server_port))) { |
635 | 0 | tmp += strlen(rtsp_sps_server_port); |
636 | 0 | if (sscanf(tmp, "%u-%u", &s_data_port, &s_mon_port) < 1) { |
637 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_port); |
638 | 0 | return; |
639 | 0 | } |
640 | 0 | } |
641 | 0 | else if ((tmp = strstr(buf, rtsp_sps_dest_addr))) { |
642 | 0 | tmp += strlen(rtsp_sps_dest_addr); |
643 | 0 | if (sscanf(tmp, "\":%u\"", &s_data_port) == 1) { |
644 | | /* :9 mean ignore */ |
645 | 0 | if (s_data_port == 9) { |
646 | 0 | s_data_port = 0; |
647 | 0 | } |
648 | 0 | } |
649 | 0 | else if (sscanf(tmp, "\"%u.%u.%u.%u:%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4, &s_data_port) == 5) { |
650 | 0 | char *tmp2; |
651 | 0 | char *tmp3; |
652 | | |
653 | | /* Skip leading " */ |
654 | 0 | tmp++; |
655 | 0 | tmp2=strstr(tmp,":"); |
656 | 0 | tmp3=g_strndup(tmp,tmp2-tmp); |
657 | 0 | if (!str_to_ip(tmp3, &ip4_addr)) { |
658 | 0 | g_free(tmp3); |
659 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_ip_address); |
660 | 0 | return; |
661 | 0 | } |
662 | 0 | set_address(&dst_addr, AT_IPv4, 4, &ip4_addr); |
663 | 0 | g_free(tmp3); |
664 | 0 | } |
665 | 0 | else if (sscanf(tmp, "\"%u.%u.%u.%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4) == 4) { |
666 | 0 | char *tmp2; |
667 | 0 | char *tmp3; |
668 | | |
669 | | /* Skip leading " */ |
670 | 0 | tmp++; |
671 | 0 | tmp2=strstr(tmp,"\""); |
672 | 0 | tmp3=g_strndup(tmp,tmp2-tmp); |
673 | 0 | if (!str_to_ip(tmp3, &ip4_addr)) { |
674 | 0 | g_free(tmp3); |
675 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_ip_address); |
676 | 0 | return; |
677 | 0 | } |
678 | 0 | set_address(&dst_addr, AT_IPv4, 4, &ip4_addr); |
679 | 0 | g_free(tmp3); |
680 | 0 | } |
681 | 0 | else |
682 | 0 | { |
683 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_server_port); |
684 | 0 | return; |
685 | 0 | } |
686 | 0 | } |
687 | | |
688 | | |
689 | | /* Look for client port */ |
690 | 0 | if ((tmp = strstr(buf, rtsp_cps_server_port))) { |
691 | 0 | tmp += strlen(rtsp_cps_server_port); |
692 | 0 | if (sscanf(tmp, "%u-%u", &c_data_port, &c_mon_port) < 1) { |
693 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_client_port); |
694 | 0 | return; |
695 | 0 | } |
696 | 0 | } |
697 | 0 | else if ((tmp = strstr(buf, rtsp_cps_src_addr))) { |
698 | 0 | tmp += strlen(rtsp_cps_src_addr); |
699 | 0 | if (sscanf(tmp, "\"%u.%u.%u.%u:%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4, &c_data_port) == 5) { |
700 | 0 | char *tmp2; |
701 | 0 | char *tmp3; |
702 | | |
703 | | /* Skip leading " */ |
704 | 0 | tmp++; |
705 | 0 | tmp2=strstr(tmp,":"); |
706 | 0 | tmp3=g_strndup(tmp,tmp2-tmp); |
707 | 0 | if (!str_to_ip(tmp3, &ip4_addr)) { |
708 | 0 | g_free(tmp3); |
709 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_client_ip_address); |
710 | 0 | return; |
711 | 0 | } |
712 | 0 | set_address(&src_addr, AT_IPv4, 4, &ip4_addr); |
713 | 0 | g_free(tmp3); |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | 0 | if (setup_info && setup_info->base_uri) { |
718 | 0 | rtp_dyn_payload = sdp_get_rtsp_media_desc(setup_info->base_uri); |
719 | 0 | } |
720 | | |
721 | | /* Deal with RTSP TCP-interleaved conversations. */ |
722 | 0 | tmp = strstr(buf, rtsp_inter); |
723 | 0 | if (tmp != NULL) { |
724 | 0 | rtsp_conversation_data_t *data; |
725 | 0 | unsigned s_data_chan, s_mon_chan; |
726 | 0 | int i; |
727 | | |
728 | | /* Move tmp to beyond interleaved string */ |
729 | 0 | tmp += strlen(rtsp_inter); |
730 | | /* Look for channel number(s) */ |
731 | 0 | i = sscanf(tmp, "%u-%u", &s_data_chan, &s_mon_chan); |
732 | 0 | if (i < 1) |
733 | 0 | { |
734 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_bad_interleaved_channel); |
735 | 0 | return; |
736 | 0 | } |
737 | | |
738 | | /* At least data channel present, look for conversation (presumably TCP) */ |
739 | 0 | data = get_rtsp_conversation_data(NULL, pinfo); |
740 | | |
741 | | /* XXX - This doesn't set up the rtp conversation data, including RTP |
742 | | * dynamic payload types and setup info. Two possible approaches: |
743 | | * 1) The RTP dissector have a function that attaches dynamic payload |
744 | | * type and setup info to the TCP conversation but does *not* set |
745 | | * the conversation dissector (TCP needs to call the RTSP dissector |
746 | | * first for interleaved data). |
747 | | * 2) Define a CONVERSATION_RTSP type and change the RTP dissector to |
748 | | * do something other than only look for conversations that match |
749 | | * conversation_pt_to_conversation_type(pinfo->ptype). |
750 | | * |
751 | | * The former needs to attach a "bundled" rtp_dyn_payload_t that |
752 | | * includes mapping for the payload types of all possible channels; |
753 | | * this usually happens when RTSP is used because the media descriptor |
754 | | * port is usually 0, but we'd want to ensure it. (It also would not |
755 | | * work if multiple sessions were SETUP simultaneously and media |
756 | | * descriptors with different meanings for the same RTP dynamic payload |
757 | | * type were PLAYed on different interleaved channels simultaneously) |
758 | | */ |
759 | | |
760 | | /* Now set the dissector handle of the interleaved channel |
761 | | according to the transport protocol used */ |
762 | 0 | if (rtp_tcp_transport) |
763 | 0 | { |
764 | 0 | if (s_data_chan < RTSP_MAX_INTERLEAVED) { |
765 | 0 | data->interleaved[s_data_chan].dissector = |
766 | 0 | rtp_handle; |
767 | 0 | } |
768 | 0 | if (i > 1 && s_mon_chan < RTSP_MAX_INTERLEAVED) { |
769 | 0 | data->interleaved[s_mon_chan].dissector = |
770 | 0 | rtcp_handle; |
771 | 0 | } |
772 | 0 | } |
773 | 0 | else if (rdt_transport) |
774 | 0 | { |
775 | 0 | if (s_data_chan < RTSP_MAX_INTERLEAVED) { |
776 | 0 | data->interleaved[s_data_chan].dissector = |
777 | 0 | rdt_handle; |
778 | 0 | } |
779 | 0 | } |
780 | 0 | return; |
781 | 0 | } |
782 | | |
783 | | /* Noninterleaved options follow */ |
784 | | /* |
785 | | * We only want to match on the destination address, not the |
786 | | * source address, because the server might send back a packet |
787 | | * from an address other than the address to which its client |
788 | | * sent the packet, so we construct a conversation with no |
789 | | * second address. |
790 | | */ |
791 | 0 | else if (rtp_udp_transport) |
792 | 0 | { |
793 | | /* RTP only if indicated */ |
794 | 0 | if (c_data_port) |
795 | 0 | { |
796 | 0 | srtp_add_address(pinfo, PT_UDP, &dst_addr, c_data_port, s_data_port, |
797 | 0 | "RTSP", pinfo->num, is_video, rtp_dyn_payload, NULL, setup_info); |
798 | 0 | } |
799 | 0 | else if (s_data_port) |
800 | 0 | { |
801 | 0 | srtp_add_address(pinfo, PT_UDP, &src_addr, s_data_port, 0, |
802 | 0 | "RTSP", pinfo->num, is_video, rtp_dyn_payload, NULL, setup_info); |
803 | 0 | } |
804 | | |
805 | | /* RTCP only if indicated */ |
806 | 0 | if (c_mon_port) |
807 | 0 | { |
808 | 0 | rtcp_add_address(pinfo, &pinfo->dst, c_mon_port, s_mon_port, |
809 | 0 | "RTSP", pinfo->num); |
810 | 0 | } |
811 | 0 | } |
812 | 0 | else if (rtp_tcp_transport) |
813 | 0 | { |
814 | | /* RTP only if indicated */ |
815 | 0 | srtp_add_address(pinfo, PT_TCP, &src_addr, c_data_port, s_data_port, |
816 | 0 | "RTSP", pinfo->num, is_video, rtp_dyn_payload, NULL, setup_info); |
817 | 0 | } |
818 | 0 | else if (rdt_transport) |
819 | 0 | { |
820 | | /* Real Data Transport */ |
821 | | /* XXX - The RDT dissector considers this signed for some reason. */ |
822 | 0 | rdt_add_address(pinfo, &pinfo->dst, c_data_port, s_data_port, |
823 | 0 | "RTSP", (int)rdt_feature_level); |
824 | 0 | } |
825 | 0 | return; |
826 | 0 | } |
827 | | |
828 | | static const char rtsp_content_length[] = "Content-Length:"; |
829 | | |
830 | | static bool |
831 | | rtsp_get_content_length(const char *line_begin, uint32_t *content_length) |
832 | 0 | { |
833 | 0 | const char *tmp; |
834 | 0 | const char *p; |
835 | | |
836 | | /* We only call this if HDR_MATCHES(rtsp_content_length)) is true, |
837 | | * so line_len has already been checked to be long enough. The |
838 | | * line has been extracted as a null terminated string from the |
839 | | * packet data. */ |
840 | |
|
841 | 0 | tmp = line_begin + STRLEN_CONST(rtsp_content_length); |
842 | | /* Allow trailing spaces but not anything else. */ |
843 | 0 | if (!ws_strtou32(tmp, &p, content_length) || (*p != '\0' && !g_ascii_isspace(*p))) { |
844 | 0 | *content_length = 0; |
845 | 0 | return false; /* not a valid number */ |
846 | 0 | } |
847 | 0 | return true; |
848 | 0 | } |
849 | | |
850 | | static const char rtsp_Session[] = "Session:"; |
851 | | static const char rtsp_X_Vig_Msisdn[] = "X-Vig-Msisdn"; |
852 | | |
853 | | static int |
854 | | dissect_rtspmessage(tvbuff_t *tvb, unsigned offset, packet_info *pinfo, proto_tree *tree) |
855 | 48 | { |
856 | 48 | proto_tree *rtsp_tree = NULL; |
857 | 48 | proto_tree *req_tree = NULL; |
858 | 48 | proto_tree *sub_tree = NULL; |
859 | 48 | proto_item *ti_top = NULL; |
860 | 48 | proto_item *ti = NULL; |
861 | 48 | const char *line; |
862 | 48 | unsigned next_offset; |
863 | 48 | unsigned orig_offset; |
864 | 48 | unsigned first_linelen, linelen; |
865 | 48 | unsigned line_end_offset; |
866 | 48 | unsigned colon_offset; |
867 | 48 | bool is_request_or_reply; |
868 | 48 | bool body_requires_content_len; |
869 | 48 | bool saw_req_resp_or_header; |
870 | 48 | unsigned char c; |
871 | 48 | rtsp_type_t rtsp_type_packet; |
872 | 48 | rtsp_type_t rtsp_type_line; |
873 | 48 | bool is_header; |
874 | 48 | unsigned datalen; |
875 | 48 | uint32_t content_length; |
876 | 48 | bool cont_len_found; |
877 | 48 | unsigned reported_datalen; |
878 | 48 | unsigned value_offset; |
879 | 48 | unsigned value_len; |
880 | 48 | e164_info_t e164_info; |
881 | 48 | uint32_t rdt_feature_level = 0; |
882 | 48 | char *media_type_str_lower_case = NULL; |
883 | 48 | unsigned semi_colon_offset; |
884 | 48 | unsigned par_end_offset; |
885 | 48 | char *frame_label = NULL; |
886 | 48 | char *session_id = NULL; |
887 | 48 | voip_packet_info_t *stat_info = NULL; |
888 | 48 | bool cseq_valid = false; |
889 | 48 | uint32_t cseq = 0; |
890 | 48 | char *content_base = NULL; |
891 | 48 | char *content_location = NULL; |
892 | 48 | char *request_uri = NULL; |
893 | 48 | char *base_uri = NULL; |
894 | 48 | const char *transport_line = NULL; |
895 | 48 | unsigned transport_linelen; |
896 | 48 | sdp_setup_info_t *setup_info = NULL; |
897 | 48 | rtsp_info_value_t *rtsp_stat_info; |
898 | | |
899 | 48 | rtsp_stat_info = wmem_new(pinfo->pool, rtsp_info_value_t); |
900 | 48 | rtsp_stat_info->framenum = pinfo->num; |
901 | 48 | rtsp_stat_info->response_code = 0; |
902 | 48 | rtsp_stat_info->request_method = NULL; |
903 | 48 | rtsp_stat_info->request_uri = NULL; |
904 | 48 | rtsp_stat_info->rtsp_host = NULL; |
905 | | |
906 | | /* |
907 | | * Is this a request or response? |
908 | | * |
909 | | * Note that "tvb_find_line_end_remaining()" will return a value that |
910 | | * is not longer than what's in the buffer, so the |
911 | | * "tvb_get_ptr()" call won't throw an exception. |
912 | | */ |
913 | 48 | tvb_find_line_end_remaining(tvb, offset, &first_linelen, &next_offset); |
914 | | |
915 | | /* |
916 | | * Is the first line a request or response? |
917 | | */ |
918 | 48 | is_request_or_reply = is_rtsp_request_or_reply(tvb_new_subset_length(tvb, offset, first_linelen), first_linelen, |
919 | 48 | &rtsp_type_packet, rtsp_stat_info, pinfo->pool); |
920 | 48 | if (is_request_or_reply) { |
921 | | /* |
922 | | * Yes, it's a request or response. |
923 | | * Do header desegmentation if we've been told to, |
924 | | * and do body desegmentation if we've been told to and |
925 | | * we find a Content-Length header. |
926 | | * |
927 | | * RFC 7826, Section 18.17. requires Content-Length and |
928 | | * assumes zero if missing. |
929 | | */ |
930 | 0 | if (!req_resp_hdrs_do_reassembly(tvb, offset, pinfo, |
931 | 0 | rtsp_desegment_headers, rtsp_desegment_body, false, NULL, |
932 | 0 | NULL, NULL)) { |
933 | | /* |
934 | | * More data needed for desegmentation. |
935 | | */ |
936 | 0 | return -1; |
937 | 0 | } |
938 | 0 | } |
939 | | |
940 | | /* |
941 | | * RFC 2326 says that a content length must be specified |
942 | | * in requests that have a body, although section 4.4 speaks |
943 | | * of a server closing the connection indicating the end of |
944 | | * a reply body. |
945 | | * |
946 | | * To support pipelining, we check if line behind blank line |
947 | | * looks like RTSP header. If so, we process rest of packet with |
948 | | * RTSP loop. |
949 | | * |
950 | | * If no, we assume that an absent content length in a request means |
951 | | * that we don't have a body, and that an absent content length |
952 | | * in a reply means that the reply body runs to the end of |
953 | | * the connection. If the first line is neither, we assume |
954 | | * that whatever follows a blank line should be treated as a |
955 | | * body; there's not much else we can do, as we're jumping |
956 | | * into the message in the middle. |
957 | | * |
958 | | * XXX - if there was no Content-Length entity header, we should |
959 | | * accumulate all data until the end of the connection. |
960 | | * That'd require that the TCP dissector call subdissectors |
961 | | * for all frames with FIN, even if they contain no data, |
962 | | * which would require subdissectors to deal intelligently |
963 | | * with empty segments. |
964 | | */ |
965 | 48 | if (rtsp_type_packet == RTSP_REQUEST) |
966 | 0 | body_requires_content_len = true; |
967 | 48 | else |
968 | 48 | body_requires_content_len = false; |
969 | | |
970 | 48 | line = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset, first_linelen, ENC_UTF_8); |
971 | 48 | if (is_request_or_reply) { |
972 | 0 | if ( rtsp_type_packet == RTSP_REPLY ) { |
973 | 0 | frame_label = wmem_strdup_printf(pinfo->pool, |
974 | 0 | "Reply: %s", format_text(pinfo->pool, line, first_linelen)); |
975 | 0 | } |
976 | 0 | else { |
977 | 0 | frame_label = format_text(pinfo->pool, line, first_linelen); |
978 | 0 | } |
979 | 0 | } |
980 | | |
981 | 48 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTSP"); |
982 | | /* |
983 | | * Put the first line from the buffer into the summary |
984 | | * if it's an RTSP request or reply (but leave out the |
985 | | * line terminator). |
986 | | * Otherwise, just call it a continuation. |
987 | | * |
988 | | * Note that "tvb_find_line_end_remaining()" will return a value that |
989 | | * is not longer than what's in the buffer, so the |
990 | | * "tvb_get_ptr()" call won't throw an exception. |
991 | | */ |
992 | 48 | if (is_request_or_reply) |
993 | 0 | if ( rtsp_type_packet == RTSP_REPLY ) { |
994 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Reply: "); |
995 | 0 | col_append_str(pinfo->cinfo, COL_INFO, |
996 | 0 | format_text(pinfo->pool, line, first_linelen)); |
997 | 0 | } |
998 | 0 | else { |
999 | 0 | col_add_str(pinfo->cinfo, COL_INFO, |
1000 | 0 | format_text(pinfo->pool, line, first_linelen)); |
1001 | 0 | } |
1002 | | |
1003 | 48 | else |
1004 | 48 | col_set_str(pinfo->cinfo, COL_INFO, "Continuation"); |
1005 | | |
1006 | 48 | orig_offset = offset; |
1007 | 48 | if (tree) { |
1008 | 48 | ti_top = proto_tree_add_item(tree, proto_rtsp, tvb, offset, -1, |
1009 | 48 | ENC_NA); |
1010 | 48 | rtsp_tree = proto_item_add_subtree(ti_top, ett_rtsp); |
1011 | 48 | } |
1012 | | |
1013 | | /* |
1014 | | * We haven't yet seen a Content-Length header. |
1015 | | */ |
1016 | 48 | cont_len_found = false; |
1017 | | |
1018 | | /* |
1019 | | * Process the packet data, a line at a time. |
1020 | | */ |
1021 | 48 | saw_req_resp_or_header = false; /* haven't seen anything yet */ |
1022 | 74 | while (tvb_offset_exists(tvb, offset)) { |
1023 | | /* |
1024 | | * We haven't yet concluded that this is a header. |
1025 | | */ |
1026 | 74 | is_header = false; |
1027 | | |
1028 | | /* |
1029 | | * Find the end of the line. |
1030 | | */ |
1031 | 74 | if (!tvb_find_line_end_remaining(tvb, offset, &linelen, &next_offset)) { |
1032 | 18 | return -1; |
1033 | 18 | } |
1034 | 56 | line_end_offset = offset + linelen; |
1035 | | |
1036 | | |
1037 | 56 | tvb_find_uint8_length(tvb, offset, linelen, ':', &colon_offset); |
1038 | | |
1039 | | |
1040 | | /* |
1041 | | * Get a buffer that refers to the line. |
1042 | | */ |
1043 | 56 | line = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset, linelen, ENC_UTF_8); |
1044 | | |
1045 | | /* |
1046 | | * OK, does it look like an RTSP request or response? |
1047 | | */ |
1048 | 56 | is_request_or_reply = is_rtsp_request_or_reply(tvb_new_subset_length(tvb, offset, linelen), linelen, &rtsp_type_line, |
1049 | 56 | rtsp_stat_info, pinfo->pool); |
1050 | 56 | if (is_request_or_reply) |
1051 | 0 | goto is_rtsp; |
1052 | | |
1053 | | /* |
1054 | | * No. Does it look like a blank line (as would appear |
1055 | | * at the end of an RTSP request)? |
1056 | | */ |
1057 | 56 | if (linelen == 0) |
1058 | 4 | goto is_rtsp; /* Yes. */ |
1059 | | |
1060 | | /* |
1061 | | * No. Does it look like a header? |
1062 | | */ |
1063 | 356 | for (unsigned current_offset = offset; current_offset < next_offset; ++current_offset) { |
1064 | 356 | c = tvb_get_uint8(tvb, current_offset); |
1065 | | |
1066 | | /* |
1067 | | * This must be a CHAR, and must not be a CTL, to be part |
1068 | | * of a token; that means it must be printable ASCII. |
1069 | | * |
1070 | | * XXX - what about leading LWS on continuation |
1071 | | * lines of a header? |
1072 | | */ |
1073 | 356 | if (!g_ascii_isprint(c)) |
1074 | 14 | break; |
1075 | | |
1076 | 342 | switch (c) { |
1077 | | |
1078 | 1 | case '(': |
1079 | 1 | case ')': |
1080 | 2 | case '<': |
1081 | 3 | case '>': |
1082 | 3 | case '@': |
1083 | 4 | case ',': |
1084 | 5 | case ';': |
1085 | 6 | case '\\': |
1086 | 7 | case '"': |
1087 | 7 | case '/': |
1088 | 8 | case '[': |
1089 | 9 | case ']': |
1090 | 10 | case '?': |
1091 | 12 | case '=': |
1092 | 12 | case '{': |
1093 | 12 | case '}': |
1094 | | /* |
1095 | | * It's a tspecial, so it's not |
1096 | | * part of a token, so it's not |
1097 | | * a field name for the beginning |
1098 | | * of a header. |
1099 | | */ |
1100 | 12 | goto not_rtsp; |
1101 | | |
1102 | 17 | case ':': |
1103 | | /* |
1104 | | * This ends the token; we consider |
1105 | | * this to be a header. |
1106 | | */ |
1107 | 17 | is_header = true; |
1108 | 17 | goto is_rtsp; |
1109 | | |
1110 | 9 | case ' ': |
1111 | 9 | case '\t': |
1112 | | /* |
1113 | | * LWS (RFC-2616, 4.2); continue the previous |
1114 | | * header. |
1115 | | */ |
1116 | 9 | goto is_rtsp; |
1117 | 342 | } |
1118 | 342 | } |
1119 | | |
1120 | | /* |
1121 | | * We haven't seen the colon, but everything else looks |
1122 | | * OK for a header line. |
1123 | | * |
1124 | | * If we've already seen an RTSP request or response |
1125 | | * line, or a header line, and we're at the end of |
1126 | | * the tvbuff, we assume this is an incomplete header |
1127 | | * line. (We quit this loop after seeing a blank line, |
1128 | | * so if we've seen a request or response line, or a |
1129 | | * header line, this is probably more of the request |
1130 | | * or response we're presumably seeing. There is some |
1131 | | * risk of false positives, but the same applies for |
1132 | | * full request or response lines or header lines, |
1133 | | * although that's less likely.) |
1134 | | * |
1135 | | * We throw an exception in that case, by checking for |
1136 | | * the existence of the next byte after the last one |
1137 | | * in the line. If it exists, "tvb_ensure_bytes_exist()" |
1138 | | * throws no exception, and we fall through to the |
1139 | | * "not RTSP" case. If it doesn't exist, |
1140 | | * "tvb_ensure_bytes_exist()" will throw the appropriate |
1141 | | * exception. |
1142 | | */ |
1143 | 14 | if (saw_req_resp_or_header) |
1144 | 3 | tvb_ensure_bytes_exist(tvb, offset, linelen + 1); |
1145 | | |
1146 | 26 | not_rtsp: |
1147 | | /* |
1148 | | * We don't consider this part of an RTSP request or |
1149 | | * reply, so we don't display it. |
1150 | | */ |
1151 | 26 | break; |
1152 | | |
1153 | 30 | is_rtsp: |
1154 | | /* |
1155 | | * Process this line. |
1156 | | */ |
1157 | 30 | if (linelen == 0) { |
1158 | | /* |
1159 | | * This is a blank line, which means that |
1160 | | * whatever follows it isn't part of this |
1161 | | * request or reply. |
1162 | | */ |
1163 | 4 | proto_tree_add_format_text(rtsp_tree, tvb, offset, next_offset - offset); |
1164 | 4 | offset = next_offset; |
1165 | 4 | break; |
1166 | 4 | } |
1167 | | |
1168 | | /* |
1169 | | * Not a blank line - either a request, a reply, or a header |
1170 | | * line. |
1171 | | */ |
1172 | 26 | saw_req_resp_or_header = true; |
1173 | | |
1174 | 26 | switch (rtsp_type_line) |
1175 | 26 | { |
1176 | 0 | case RTSP_REQUEST: |
1177 | | /* Add a tree for this request */ |
1178 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_request, tvb, offset, |
1179 | 0 | (int) (next_offset - offset), |
1180 | 0 | tvb_format_text(pinfo->pool, tvb, offset, (int) (next_offset - offset))); |
1181 | 0 | req_tree = proto_item_add_subtree(ti, ett_rtsp_method); |
1182 | 0 | request_uri = process_rtsp_request(tvb_new_subset_length(tvb, offset, linelen), linelen, pinfo, req_tree); |
1183 | 0 | break; |
1184 | | |
1185 | 0 | case RTSP_REPLY: |
1186 | | /* Add a tree for this response */ |
1187 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_response, tvb, offset, |
1188 | 0 | (int) (next_offset - offset), |
1189 | 0 | tvb_format_text(pinfo->pool, tvb, offset, (int) (next_offset - offset))); |
1190 | 0 | req_tree = proto_item_add_subtree(ti, ett_rtsp_method); |
1191 | 0 | process_rtsp_reply(tvb_new_subset_length(tvb, offset, linelen), linelen, pinfo, req_tree); |
1192 | 0 | break; |
1193 | | |
1194 | 26 | case RTSP_NOT_FIRST_LINE: |
1195 | | /* Drop through, it may well be a header line */ |
1196 | 26 | break; |
1197 | 26 | } |
1198 | | |
1199 | 26 | if (is_header) |
1200 | 17 | { |
1201 | | /* We know that colon_offset must be set */ |
1202 | | |
1203 | | /* Skip whitespace after the colon. */ |
1204 | 17 | value_offset = colon_offset + 1; |
1205 | 17 | while ((value_offset < line_end_offset) && |
1206 | 17 | ((c = tvb_get_uint8(tvb, value_offset)) == ' ' || c == '\t')) |
1207 | 0 | { |
1208 | 0 | value_offset++; |
1209 | 0 | } |
1210 | 17 | value_len = line_end_offset - value_offset; |
1211 | | |
1212 | | /* |
1213 | | * Process some headers specially. |
1214 | | */ |
1215 | 17 | #define HDR_MATCHES(header) \ |
1216 | 153 | ( (size_t)linelen > STRLEN_CONST(header) && \ |
1217 | 153 | g_ascii_strncasecmp(line, (header), STRLEN_CONST(header)) == 0) |
1218 | | |
1219 | 17 | if (HDR_MATCHES(rtsp_transport)) |
1220 | 0 | { |
1221 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_transport, tvb, |
1222 | 0 | offset, linelen, |
1223 | 0 | tvb_format_text(pinfo->pool, tvb, value_offset, |
1224 | 0 | value_len)); |
1225 | | |
1226 | | /* Setup the conversation after parsing all the headers. */ |
1227 | 0 | transport_line = line; |
1228 | 0 | transport_linelen = linelen; |
1229 | 17 | } else if (HDR_MATCHES(rtsp_content_type)) |
1230 | 0 | { |
1231 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_content_type, |
1232 | 0 | tvb, offset, linelen, |
1233 | 0 | tvb_format_text(pinfo->pool, tvb, value_offset, |
1234 | 0 | value_len)); |
1235 | |
|
1236 | 0 | offset = offset + (int)STRLEN_CONST(rtsp_content_type); |
1237 | | /* Skip wsp */ |
1238 | 0 | offset = tvb_skip_wsp(tvb, offset, value_len); |
1239 | 0 | if (tvb_find_uint8_length(tvb, value_offset, value_len, ';', &semi_colon_offset)) { |
1240 | | /* m-parameter present */ |
1241 | 0 | par_end_offset = tvb_skip_wsp_return(tvb, semi_colon_offset-1); |
1242 | 0 | value_len = par_end_offset - offset; |
1243 | 0 | } |
1244 | |
|
1245 | 0 | media_type_str_lower_case = ascii_strdown_inplace( |
1246 | 0 | (char *)tvb_get_string_enc(pinfo->pool, tvb, offset, value_len, ENC_ASCII)); |
1247 | 17 | } else if (HDR_MATCHES(rtsp_content_length)) |
1248 | 0 | { |
1249 | 0 | uint32_t clength; |
1250 | 0 | bool clength_valid; |
1251 | 0 | clength_valid = tvb_get_string_uint(tvb, value_offset, value_len, ENC_STR_DEC, &clength, NULL); |
1252 | 0 | ti = proto_tree_add_uint(rtsp_tree, hf_rtsp_content_length, tvb, offset, linelen, clength); |
1253 | 0 | if (!clength_valid) |
1254 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_content_length_invalid); |
1255 | | |
1256 | | /* |
1257 | | * Only the amount specified by the |
1258 | | * Content-Length: header should be treated |
1259 | | * as payload. |
1260 | | */ |
1261 | 0 | cont_len_found = rtsp_get_content_length(line, &content_length); |
1262 | |
|
1263 | 17 | } else if (HDR_MATCHES(rtsp_Session)) |
1264 | 0 | { |
1265 | 0 | session_id = tvb_format_text(pinfo->pool, tvb, value_offset, value_len); |
1266 | | /* Put the value into the protocol tree */ |
1267 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_session, tvb, |
1268 | 0 | offset, linelen, |
1269 | 0 | session_id); |
1270 | |
|
1271 | 17 | } else if (HDR_MATCHES(rtsp_X_Vig_Msisdn)) { |
1272 | | /* |
1273 | | * Extract the X_Vig_Msisdn string |
1274 | | */ |
1275 | 0 | if (colon_offset != 0) |
1276 | 0 | { |
1277 | | /* Put the value into the protocol tree */ |
1278 | 0 | ti = proto_tree_add_string(rtsp_tree, hf_rtsp_X_Vig_Msisdn,tvb, |
1279 | 0 | offset, linelen , |
1280 | 0 | tvb_format_text(pinfo->pool, tvb, value_offset, value_len)); |
1281 | 0 | sub_tree = proto_item_add_subtree(ti, ett_rtsp_method); |
1282 | |
|
1283 | 0 | e164_info.e164_number_type = CALLING_PARTY_NUMBER; |
1284 | 0 | e164_info.nature_of_address = 0; |
1285 | |
|
1286 | 0 | e164_info.E164_number_str = (char*)tvb_get_string_enc(pinfo->pool, tvb, value_offset, |
1287 | 0 | value_len, ENC_ASCII); |
1288 | 0 | e164_info.E164_number_length = value_len; |
1289 | 0 | dissect_e164_number(tvb, sub_tree, value_offset, |
1290 | 0 | value_len, e164_info); |
1291 | 0 | } |
1292 | 17 | } else if (HDR_MATCHES(rtsp_rdt_feature_level)) |
1293 | 0 | { |
1294 | 0 | bool rdt_feature_level_valid; |
1295 | 0 | rdt_feature_level_valid = tvb_get_string_uint(tvb, value_offset, value_len, ENC_STR_DEC, &rdt_feature_level, NULL); |
1296 | 0 | ti = proto_tree_add_uint(rtsp_tree, hf_rtsp_rdtfeaturelevel, |
1297 | 0 | tvb, offset, linelen, rdt_feature_level); |
1298 | 0 | if (!rdt_feature_level_valid) |
1299 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_rdtfeaturelevel_invalid); |
1300 | 17 | } else if (HDR_MATCHES(rtsp_cseq)) |
1301 | 0 | { |
1302 | 0 | cseq_valid = tvb_get_string_uint(tvb, value_offset, value_len, ENC_STR_DEC, &cseq, NULL); |
1303 | 0 | ti = proto_tree_add_uint(rtsp_tree, hf_rtsp_cseq, tvb, offset, linelen, cseq); |
1304 | 0 | if (!cseq_valid) { |
1305 | 0 | expert_add_info(pinfo, ti, &ei_rtsp_cseq_invalid); |
1306 | 0 | } |
1307 | 17 | } else if (HDR_MATCHES(rtsp_content_base)) |
1308 | 0 | { |
1309 | 0 | content_base = (char *)tvb_get_string_enc(pinfo->pool, tvb, value_offset, value_len, ENC_UTF_8); |
1310 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_content_base, |
1311 | 0 | tvb, offset, linelen, content_base); |
1312 | 17 | } else if (HDR_MATCHES(rtsp_content_location)) |
1313 | 0 | { |
1314 | 0 | content_location = (char *)tvb_get_string_enc(pinfo->pool, tvb, value_offset, value_len, ENC_UTF_8); |
1315 | 0 | proto_tree_add_string(rtsp_tree, hf_rtsp_content_location, |
1316 | 0 | tvb, offset, linelen, content_location); |
1317 | 0 | } |
1318 | 17 | else |
1319 | 17 | { |
1320 | | /* Default case for headers. Show line as text */ |
1321 | 17 | proto_tree_add_format_text(rtsp_tree, tvb, offset, next_offset - offset); |
1322 | 17 | } |
1323 | 17 | } |
1324 | 9 | else if (rtsp_type_line == RTSP_NOT_FIRST_LINE) |
1325 | 9 | { |
1326 | | /* Catch-all for all other lines... Show line as text. |
1327 | | TODO: should these be shown as errors? */ |
1328 | 9 | proto_tree_add_format_text(rtsp_tree, tvb, offset, next_offset - offset); |
1329 | 9 | } |
1330 | | |
1331 | 26 | offset = next_offset; |
1332 | 26 | } |
1333 | | |
1334 | 30 | if (cseq_valid) { |
1335 | 0 | rtsp_conversation_data_t *conv_data = get_rtsp_conversation_data(NULL, pinfo); |
1336 | 0 | rtsp_req_resp_t *curr_req_resp = wmem_map_lookup(conv_data->req_resp_map, GUINT_TO_POINTER(cseq)); |
1337 | 0 | if (!curr_req_resp) { |
1338 | 0 | curr_req_resp = wmem_new0(wmem_file_scope(), rtsp_req_resp_t); |
1339 | 0 | wmem_map_insert(conv_data->req_resp_map, GUINT_TO_POINTER(cseq), curr_req_resp); |
1340 | 0 | } |
1341 | 0 | if (rtsp_type_packet == RTSP_REQUEST) { |
1342 | 0 | if (curr_req_resp->req_frame == 0) { |
1343 | 0 | curr_req_resp->req_frame = pinfo->num; |
1344 | 0 | } |
1345 | 0 | if (curr_req_resp->resp_frame) { |
1346 | 0 | proto_tree_add_uint(req_tree, hf_rtsp_response_in, tvb, 0, 0, curr_req_resp->resp_frame); |
1347 | 0 | } |
1348 | 0 | if (request_uri && !curr_req_resp->request_uri) { |
1349 | 0 | curr_req_resp->request_uri = wmem_strdup(wmem_file_scope(), request_uri); |
1350 | 0 | } |
1351 | 0 | } else { |
1352 | 0 | if (curr_req_resp->resp_frame == 0) { |
1353 | 0 | curr_req_resp->resp_frame = pinfo->num; |
1354 | 0 | } |
1355 | 0 | if (curr_req_resp->request_uri) { |
1356 | 0 | ti = proto_tree_add_string(req_tree, hf_rtsp_url, tvb, 0, 0, curr_req_resp->request_uri); |
1357 | 0 | proto_item_set_generated(ti); |
1358 | 0 | } |
1359 | 0 | if (curr_req_resp->req_frame) { |
1360 | 0 | proto_tree_add_uint(req_tree, hf_rtsp_response_to, tvb, 0, 0, curr_req_resp->req_frame); |
1361 | 0 | } |
1362 | 0 | } |
1363 | 0 | if (curr_req_resp->request_uri) { |
1364 | 0 | base_uri = wmem_ascii_strdown(pinfo->pool, curr_req_resp->request_uri, -1); |
1365 | 0 | } |
1366 | 0 | } |
1367 | | |
1368 | 30 | if (content_base) { |
1369 | 0 | base_uri = content_base; |
1370 | 30 | } else if (content_location) { |
1371 | | /* XXX - Content-Location itself can be relative to the request_uri, at |
1372 | | * least according to RFC 7826. (RTSP 2.0 is not widely implemented, but |
1373 | | * it does have useful notes on gotchas and limitations of RTSP 1.0.) |
1374 | | */ |
1375 | 0 | base_uri = content_location; |
1376 | 0 | } |
1377 | | |
1378 | 30 | if (session_id) { |
1379 | 0 | stat_info = wmem_new0(pinfo->pool, voip_packet_info_t); |
1380 | 0 | stat_info->protocol_name = wmem_strdup(pinfo->pool, "RTSP"); |
1381 | 0 | stat_info->call_id = session_id; |
1382 | 0 | stat_info->frame_label = frame_label; |
1383 | 0 | stat_info->call_state = VOIP_CALL_SETUP; |
1384 | 0 | stat_info->call_active_state = VOIP_ACTIVE; |
1385 | 0 | stat_info->frame_comment = frame_label; |
1386 | 0 | tap_queue_packet(voip_tap, pinfo, stat_info); |
1387 | 0 | } |
1388 | | |
1389 | 30 | if (transport_line) { |
1390 | | /* |
1391 | | * Based on the port numbers specified in the Transport: header, set up |
1392 | | * a conversation that will be dissected with the appropriate dissector. |
1393 | | */ |
1394 | 0 | setup_info = rtsp_create_setup_info(pinfo, session_id, base_uri); |
1395 | 0 | rtsp_create_conversation(pinfo, ti, transport_line, transport_linelen, rdt_feature_level, rtsp_type_packet, setup_info); |
1396 | 0 | } |
1397 | | |
1398 | | /* |
1399 | | * Have now read all of the lines of this message. |
1400 | | * |
1401 | | * If a content length was supplied, the amount of data to be |
1402 | | * processed as RTSP payload is the minimum of the content |
1403 | | * length and the amount of data remaining in the frame. |
1404 | | * |
1405 | | * If no content length was supplied (or if a bad content length |
1406 | | * was supplied), the amount of data to be processed is the amount |
1407 | | * of data remaining in the frame. |
1408 | | */ |
1409 | 30 | datalen = tvb_captured_length_remaining(tvb, offset); |
1410 | 30 | reported_datalen = tvb_reported_length_remaining(tvb, offset); |
1411 | 30 | if (cont_len_found == true) { |
1412 | | /* |
1413 | | * Content length specified; display only that amount |
1414 | | * as payload. |
1415 | | */ |
1416 | 0 | if (datalen > content_length) |
1417 | 0 | datalen = content_length; |
1418 | | |
1419 | | /* |
1420 | | * XXX - limit the reported length in the tvbuff we'll |
1421 | | * hand to a subdissector to be no greater than the |
1422 | | * content length. |
1423 | | * |
1424 | | * We really need both unreassembled and "how long it'd |
1425 | | * be if it were reassembled" lengths for tvbuffs, so |
1426 | | * that we throw the appropriate exceptions for |
1427 | | * "not enough data captured" (running past the length), |
1428 | | * "packet needed reassembly" (within the length but |
1429 | | * running past the unreassembled length), and |
1430 | | * "packet is malformed" (running past the reassembled |
1431 | | * length). |
1432 | | */ |
1433 | 0 | if (reported_datalen > content_length) |
1434 | 0 | reported_datalen = content_length; |
1435 | 30 | } else { |
1436 | | /* |
1437 | | * No content length specified; if this message doesn't |
1438 | | * have a body if no content length is specified, process |
1439 | | * nothing as payload. |
1440 | | */ |
1441 | 30 | if (body_requires_content_len) |
1442 | 0 | datalen = 0; |
1443 | 30 | } |
1444 | | |
1445 | 30 | if (datalen > 0) { |
1446 | | /* |
1447 | | * There's stuff left over; process it. |
1448 | | */ |
1449 | 30 | tvbuff_t *new_tvb; |
1450 | | |
1451 | | /* |
1452 | | * Now create a tvbuff for the Content-type stuff and |
1453 | | * dissect it. |
1454 | | * |
1455 | | * The amount of data to be processed that's |
1456 | | * available in the tvbuff is "datalen", which |
1457 | | * is the minimum of the amount of data left in |
1458 | | * the tvbuff and any specified content length. |
1459 | | * |
1460 | | * The amount of data to be processed that's in |
1461 | | * this frame, regardless of whether it was |
1462 | | * captured or not, is "reported_datalen", |
1463 | | */ |
1464 | 30 | new_tvb = tvb_new_subset_length(tvb, offset, reported_datalen); |
1465 | | |
1466 | | |
1467 | | /* |
1468 | | * Check if next line is RTSP message - pipelining |
1469 | | * If yes, stop processing and start next loop |
1470 | | * If no, process rest of packet with dissectors |
1471 | | */ |
1472 | 30 | tvb_find_line_end_remaining(new_tvb, 0, &first_linelen, &next_offset); |
1473 | 30 | is_request_or_reply = is_rtsp_request_or_reply(tvb_new_subset_length(new_tvb, 0, first_linelen), first_linelen, |
1474 | 30 | &rtsp_type_packet, rtsp_stat_info, pinfo->pool); |
1475 | | |
1476 | 30 | if (!is_request_or_reply){ |
1477 | 30 | setup_info = rtsp_create_setup_info(pinfo, session_id, base_uri); |
1478 | 30 | media_content_info_t content_info = { MEDIA_CONTAINER_SIP_DATA, media_type_str_lower_case, NULL, setup_info }; |
1479 | 30 | if (media_type_str_lower_case && |
1480 | 0 | dissector_try_string_with_data(media_type_dissector_table, |
1481 | 0 | media_type_str_lower_case, |
1482 | 0 | new_tvb, pinfo, rtsp_tree, true, &content_info)) { |
1483 | |
|
1484 | 30 | } else { |
1485 | | /* |
1486 | | * Fix up the top-level item so that it doesn't |
1487 | | * include the SDP stuff. |
1488 | | */ |
1489 | 30 | if (ti_top != NULL) |
1490 | 30 | proto_item_set_len(ti_top, offset); |
1491 | | |
1492 | 30 | if (tvb_get_uint8(tvb, offset) == RTSP_FRAMEHDR) { |
1493 | | /* |
1494 | | * This is interleaved stuff; don't |
1495 | | * treat it as raw data - set "datalen" |
1496 | | * to 0, so we won't skip the offset |
1497 | | * past it, which will cause our |
1498 | | * caller to process that stuff itself. |
1499 | | */ |
1500 | 0 | datalen = 0; |
1501 | 30 | } else { |
1502 | 30 | proto_tree_add_bytes_format(rtsp_tree, hf_rtsp_data, tvb, offset, |
1503 | 30 | datalen, NULL, "Data (%d bytes)", |
1504 | 30 | reported_datalen); |
1505 | 30 | } |
1506 | 30 | } |
1507 | | |
1508 | | /* |
1509 | | * We've processed "datalen" bytes worth of data |
1510 | | * (which may be no data at all); advance the |
1511 | | * offset past whatever data we've processed. |
1512 | | */ |
1513 | 30 | offset += datalen; |
1514 | 30 | } |
1515 | 30 | } |
1516 | | |
1517 | 30 | tap_queue_packet(rtsp_tap, pinfo, rtsp_stat_info); |
1518 | | |
1519 | 30 | return offset - orig_offset; |
1520 | 48 | } |
1521 | | |
1522 | | static char* |
1523 | | process_rtsp_request(tvbuff_t *tvb, unsigned linelen, packet_info *pinfo, proto_tree *tree) |
1524 | 0 | { |
1525 | 0 | unsigned ii; |
1526 | 0 | char *tmp_url; |
1527 | 0 | unsigned token_len; |
1528 | 0 | unsigned offset = 0, next_offset; |
1529 | |
|
1530 | 0 | tvb_get_token_len_length(tvb, offset, linelen, &token_len , &next_offset); |
1531 | | |
1532 | | /* Request Methods */ |
1533 | 0 | for (ii = 0; ii < RTSP_NMETHODS; ii++) { |
1534 | 0 | size_t len = strlen(rtsp_methods[ii]); |
1535 | 0 | if (len == (size_t)token_len && |
1536 | 0 | tvb_strncaseeql(tvb, offset, rtsp_methods[ii], len) == 0) |
1537 | 0 | break; |
1538 | 0 | } |
1539 | 0 | if (ii == RTSP_NMETHODS) { |
1540 | | /* |
1541 | | * We got here because "is_rtsp_request_or_reply()" returned |
1542 | | * RTSP_REQUEST, so we know one of the request methods |
1543 | | * matched, so we "can't get here". |
1544 | | */ |
1545 | 0 | DISSECTOR_ASSERT_NOT_REACHED(); |
1546 | 0 | } |
1547 | | |
1548 | | /* Add method name to tree */ |
1549 | 0 | proto_tree_add_string(tree, hf_rtsp_method, tvb, offset, |
1550 | 0 | token_len, rtsp_methods[ii]); |
1551 | | |
1552 | | /* token_len does not include the terminator. */ |
1553 | | // linelen -= token_len + 1; |
1554 | | |
1555 | | /* URL */ |
1556 | | /* next_offset is after the first space after the method name. |
1557 | | * Skip any extra spaces (though there should only be a single |
1558 | | * space according to RFC 2326s and 7230.) |
1559 | | */ |
1560 | 0 | offset = tvb_skip_wsp(tvb, next_offset, tvb_reported_length_remaining(tvb, next_offset)); |
1561 | | /* Scan to end of URL */ |
1562 | 0 | tvb_get_token_len_length(tvb, offset, tvb_reported_length_remaining(tvb, offset), &token_len, NULL); |
1563 | | /* Add URL to tree */ |
1564 | 0 | proto_tree_add_item_ret_string(tree, hf_rtsp_url, tvb, |
1565 | 0 | offset, token_len, ENC_UTF_8, pinfo->pool, (const uint8_t**)&tmp_url); |
1566 | 0 | return tmp_url; |
1567 | 0 | } |
1568 | | |
1569 | | /* Read first line of a reply message */ |
1570 | | static void |
1571 | | process_rtsp_reply(tvbuff_t *tvb, unsigned linelen, packet_info *pinfo _U_, proto_tree *tree) |
1572 | 0 | { |
1573 | 0 | unsigned status_i; |
1574 | 0 | unsigned token_len; |
1575 | 0 | unsigned offset = 0, next_offset; |
1576 | | |
1577 | | /* status code */ |
1578 | | |
1579 | | /* Skip protocol/version */ |
1580 | 0 | tvb_get_token_len_length(tvb, offset, linelen, NULL , &next_offset); |
1581 | | /* Skip spaces */ |
1582 | 0 | offset = tvb_skip_wsp(tvb, next_offset, tvb_reported_length_remaining(tvb, next_offset)); |
1583 | | |
1584 | | /* Actual code number now */ |
1585 | 0 | tvb_get_token_len_length(tvb, offset, linelen, &token_len , &next_offset); |
1586 | |
|
1587 | 0 | if (tvb_get_string_uint(tvb, offset, token_len, ENC_STR_DEC, &status_i, NULL)) { |
1588 | | /* Add field to tree */ |
1589 | 0 | proto_tree_add_uint(tree, hf_rtsp_status, tvb, offset, token_len, status_i); |
1590 | 0 | } |
1591 | | // else error (There should be a space after the status code.) |
1592 | 0 | } |
1593 | | |
1594 | | static int |
1595 | | dissect_rtsp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
1596 | 49 | { |
1597 | 49 | unsigned offset = 0; |
1598 | 49 | int len; |
1599 | | |
1600 | 80 | while (tvb_reported_length_remaining(tvb, offset) != 0) { |
1601 | | /* |
1602 | | * Add separator between multiple messages in column info text |
1603 | | */ |
1604 | 49 | if (offset > 0) { |
1605 | 0 | col_set_str(pinfo->cinfo, COL_INFO, ", "); |
1606 | 0 | col_set_fence(pinfo->cinfo, COL_INFO); |
1607 | 0 | } |
1608 | 49 | len = (tvb_get_uint8(tvb, offset) == RTSP_FRAMEHDR) |
1609 | 49 | ? dissect_rtspinterleaved(tvb, offset, pinfo, tree) |
1610 | 49 | : dissect_rtspmessage(tvb, offset, pinfo, tree); |
1611 | 49 | if (len == -1) |
1612 | 18 | break; |
1613 | 31 | offset += len; |
1614 | | |
1615 | | /* |
1616 | | * OK, we've set the Protocol and Info columns for the |
1617 | | * first RTSP message; set fence so changes are kept for |
1618 | | * subsequent RTSP messages. |
1619 | | */ |
1620 | 31 | col_set_fence(pinfo->cinfo, COL_INFO); |
1621 | 31 | } |
1622 | 49 | return tvb_captured_length(tvb); |
1623 | 49 | } |
1624 | | |
1625 | | void |
1626 | | proto_register_rtsp(void) |
1627 | 16 | { |
1628 | 16 | static int *ett[] = { |
1629 | 16 | &ett_rtspframe, |
1630 | 16 | &ett_rtsp, |
1631 | 16 | &ett_rtsp_method, |
1632 | 16 | }; |
1633 | 16 | static hf_register_info hf[] = { |
1634 | 16 | { &hf_rtsp_request, |
1635 | 16 | { "Request", "rtsp.request", FT_STRING, BASE_NONE, NULL, 0, |
1636 | 16 | NULL, HFILL }}, |
1637 | 16 | { &hf_rtsp_response, |
1638 | 16 | { "Response", "rtsp.response", FT_STRING, BASE_NONE, NULL, 0, |
1639 | 16 | NULL, HFILL }}, |
1640 | 16 | { &hf_rtsp_response_in, |
1641 | 16 | { "Response in frame", "rtsp.response_in", FT_FRAMENUM, BASE_NONE, |
1642 | 16 | FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0, NULL, HFILL }}, |
1643 | 16 | { &hf_rtsp_response_to, |
1644 | 16 | { "Response to frame", "rtsp.response_to", FT_FRAMENUM, BASE_NONE, |
1645 | 16 | FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0, NULL, HFILL }}, |
1646 | 16 | { &hf_rtsp_method, |
1647 | 16 | { "Method", "rtsp.method", FT_STRING, BASE_NONE, NULL, 0, |
1648 | 16 | NULL, HFILL }}, |
1649 | 16 | { &hf_rtsp_content_type, |
1650 | 16 | { "Content-type", "rtsp.content-type", FT_STRING, BASE_NONE, NULL, 0, |
1651 | 16 | NULL, HFILL }}, |
1652 | 16 | { &hf_rtsp_content_length, |
1653 | 16 | { "Content-length", "rtsp.content-length", FT_UINT32, BASE_DEC, NULL, 0, |
1654 | 16 | NULL, HFILL }}, |
1655 | 16 | { &hf_rtsp_url, |
1656 | 16 | { "URL", "rtsp.url", FT_STRING, BASE_NONE, NULL, 0, |
1657 | 16 | NULL, HFILL }}, |
1658 | 16 | { &hf_rtsp_status, |
1659 | 16 | { "Status", "rtsp.status", FT_UINT32, BASE_DEC, NULL, 0, |
1660 | 16 | NULL, HFILL }}, |
1661 | 16 | { &hf_rtsp_session, |
1662 | 16 | { "Session", "rtsp.session", FT_STRING, BASE_NONE, NULL, 0, |
1663 | 16 | NULL, HFILL }}, |
1664 | 16 | { &hf_rtsp_transport, |
1665 | 16 | { "Transport", "rtsp.transport", FT_STRING, BASE_NONE, NULL, 0, |
1666 | 16 | NULL, HFILL }}, |
1667 | 16 | { &hf_rtsp_rdtfeaturelevel, |
1668 | 16 | { "RDTFeatureLevel", "rtsp.rdt-feature-level", FT_UINT32, BASE_DEC, NULL, 0, |
1669 | 16 | NULL, HFILL }}, |
1670 | 16 | { &hf_rtsp_cseq, |
1671 | 16 | { "CSeq", "rtsp.cseq", FT_UINT32, BASE_DEC, NULL, 0, |
1672 | 16 | NULL, HFILL }}, |
1673 | 16 | { &hf_rtsp_content_base, |
1674 | 16 | { "Content-Base", "rtsp.content-base", FT_STRING, BASE_NONE, NULL, 0, |
1675 | 16 | NULL, HFILL }}, |
1676 | 16 | { &hf_rtsp_content_location, |
1677 | 16 | { "Content-Location", "rtsp.content-location", FT_STRING, BASE_NONE, NULL, 0, |
1678 | 16 | NULL, HFILL }}, |
1679 | 16 | { &hf_rtsp_X_Vig_Msisdn, |
1680 | 16 | { "X-Vig-Msisdn", "rtsp.X_Vig_Msisdn", FT_STRING, BASE_NONE, NULL, 0, |
1681 | 16 | NULL, HFILL }}, |
1682 | 16 | { &hf_rtsp_magic, |
1683 | 16 | { "Magic", "rtsp.magic", FT_UINT8, BASE_HEX, NULL, 0x0, |
1684 | 16 | NULL, HFILL }}, |
1685 | 16 | { &hf_rtsp_channel, |
1686 | 16 | { "Channel", "rtsp.channel", FT_UINT8, BASE_HEX, NULL, 0x0, |
1687 | 16 | NULL, HFILL }}, |
1688 | 16 | { &hf_rtsp_length, |
1689 | 16 | { "Length", "rtsp.length", FT_UINT16, BASE_DEC, NULL, 0x0, |
1690 | 16 | NULL, HFILL }}, |
1691 | 16 | { &hf_rtsp_data, |
1692 | 16 | { "Data", "rtsp.data", FT_BYTES, BASE_NONE, NULL, 0x0, |
1693 | 16 | NULL, HFILL }}, |
1694 | 16 | }; |
1695 | | |
1696 | 16 | static ei_register_info ei[] = { |
1697 | 16 | { &ei_rtsp_unknown_transport_type, |
1698 | 16 | { "rtsp.unknown_transport_type", PI_UNDECODED, PI_WARN, "Unknown transport type", EXPFILL }}, |
1699 | 16 | { &ei_rtsp_bad_server_port, |
1700 | 16 | { "rtsp.bad_server_port", PI_UNDECODED, PI_WARN, "Bad server_port", EXPFILL }}, |
1701 | 16 | { &ei_rtsp_bad_client_port, |
1702 | 16 | { "rtsp.bad_client_port", PI_UNDECODED, PI_WARN, "Bad client port", EXPFILL }}, |
1703 | 16 | { &ei_rtsp_bad_interleaved_channel, |
1704 | 16 | { "rtsp.bad_interleaved_channel", PI_UNDECODED, PI_WARN, "Bad interleaved_channel", EXPFILL }}, |
1705 | 16 | { &ei_rtsp_content_length_invalid, |
1706 | 16 | { "rtsp.content-length.invalid", PI_MALFORMED, PI_ERROR, "Invalid content length", EXPFILL }}, |
1707 | 16 | { &ei_rtsp_rdtfeaturelevel_invalid, |
1708 | 16 | { "rtsp.rdt-feature-level.invalid", PI_MALFORMED, PI_ERROR, "Invalid RDTFeatureLevel", EXPFILL }}, |
1709 | 16 | { &ei_rtsp_cseq_invalid, |
1710 | 16 | { "rtsp.cseq.invalid", PI_PROTOCOL, PI_WARN, "Invalid CSeq", EXPFILL }}, |
1711 | 16 | { &ei_rtsp_bad_server_ip_address, |
1712 | 16 | { "rtsp.bad_server_ip_address", PI_MALFORMED, PI_ERROR, "Bad server IP address", EXPFILL }}, |
1713 | 16 | { &ei_rtsp_bad_client_ip_address, |
1714 | 16 | { "rtsp.bad_client_ip_address", PI_MALFORMED, PI_ERROR, "Bad client IP address", EXPFILL }} |
1715 | 16 | }; |
1716 | | |
1717 | 16 | module_t *rtsp_module; |
1718 | 16 | expert_module_t *expert_rtsp; |
1719 | | |
1720 | 16 | proto_rtsp = proto_register_protocol("Real Time Streaming Protocol", "RTSP", "rtsp"); |
1721 | | |
1722 | 16 | proto_register_field_array(proto_rtsp, hf, array_length(hf)); |
1723 | 16 | proto_register_subtree_array(ett, array_length(ett)); |
1724 | | |
1725 | 16 | expert_rtsp = expert_register_protocol(proto_rtsp); |
1726 | 16 | expert_register_field_array(expert_rtsp, ei, array_length(ei)); |
1727 | | |
1728 | | /* Make this dissector findable by name */ |
1729 | 16 | rtsp_handle = register_dissector("rtsp", dissect_rtsp, proto_rtsp); |
1730 | | |
1731 | | /* Register our configuration options, particularly our ports */ |
1732 | | |
1733 | 16 | rtsp_module = prefs_register_protocol(proto_rtsp, NULL); |
1734 | | |
1735 | 16 | prefs_register_obsolete_preference(rtsp_module, "tcp.alternate_port"); |
1736 | | |
1737 | 16 | prefs_register_bool_preference(rtsp_module, "desegment_headers", |
1738 | 16 | "Reassemble RTSP headers spanning multiple TCP segments", |
1739 | 16 | "Whether the RTSP dissector should reassemble headers " |
1740 | 16 | "of a request spanning multiple TCP segments. " |
1741 | 16 | " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", |
1742 | 16 | &rtsp_desegment_headers); |
1743 | 16 | prefs_register_bool_preference(rtsp_module, "desegment_body", |
1744 | 16 | "Trust the \"Content-length:\" header when desegmenting", |
1745 | 16 | "Whether the RTSP dissector should use the " |
1746 | 16 | "\"Content-length:\" value to desegment the body " |
1747 | 16 | "of a request spanning multiple TCP segments", |
1748 | 16 | &rtsp_desegment_body); |
1749 | | |
1750 | | /* |
1751 | | * Heuristic dissectors SHOULD register themselves in |
1752 | | * this table using the standard heur_dissector_add() |
1753 | | * function. |
1754 | | */ |
1755 | 16 | heur_subdissector_list = register_heur_dissector_list_with_description("rtsp", "RTSP data", proto_rtsp); |
1756 | | |
1757 | | /* |
1758 | | * Register for tapping |
1759 | | */ |
1760 | 16 | rtsp_tap = register_tap("rtsp"); /* RTSP statistics tap */ |
1761 | | |
1762 | 16 | register_external_value_string("rtsp_status_code_vals", rtsp_status_code_vals); |
1763 | 16 | } |
1764 | | |
1765 | | void |
1766 | | proto_reg_handoff_rtsp(void) |
1767 | 16 | { |
1768 | 16 | rtp_handle = find_dissector_add_dependency("rtp", proto_rtsp); |
1769 | 16 | rtp_rfc4571_handle = find_dissector_add_dependency("rtp.rfc4571", proto_rtsp); |
1770 | 16 | rtcp_handle = find_dissector_add_dependency("rtcp", proto_rtsp); |
1771 | 16 | rdt_handle = find_dissector_add_dependency("rdt", proto_rtsp); |
1772 | 16 | media_type_dissector_table = find_dissector_table("media_type"); |
1773 | 16 | voip_tap = find_tap_id("voip"); |
1774 | | |
1775 | | /* Set our port number for future use */ |
1776 | 16 | dissector_add_uint_range_with_preference("tcp.port", RTSP_TCP_PORT_RANGE, rtsp_handle); |
1777 | | |
1778 | | /* XXX: Do the following only once ?? */ |
1779 | 16 | stats_tree_register("rtsp","rtsp","RTSP" STATS_TREE_MENU_SEPARATOR "Packet Counter", 0, rtsp_stats_tree_packet, rtsp_stats_tree_init, NULL ); |
1780 | | |
1781 | 16 | } |
1782 | | |
1783 | | /* |
1784 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
1785 | | * |
1786 | | * Local variables: |
1787 | | * c-basic-offset: 4 |
1788 | | * tab-width: 8 |
1789 | | * indent-tabs-mode: space |
1790 | | * End: |
1791 | | * |
1792 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
1793 | | * :indentSize=4:tabSize=8:noTabs=true: |
1794 | | */ |