2 * Routines for Redis Client/Server RESP (REdis Serialization Protocol) v2 as
3 * documented by https://redis.io/topics/protocol
5 * Copyright 2022 Ryan Doyle <ryan <AT> doylenet dot net>
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include "packet-tcp.h"
16 #include <epan/packet.h>
17 #include <epan/expert.h>
19 #define RESP_PORT 6379
21 #define RESP_TOKEN_PREFIX_LENGTH 1
22 #define MAX_ARRAY_DEPTH_TO_RECURSE 30
23 #define BULK_STRING_MAX_DISPLAY 100
24 #define RESP_NULL_STRING (-1)
25 #define RESP_NULL_ARRAY (-1)
26 #define RESP_REQUEST(pinfo) ((pinfo)->match_uint == (pinfo)->destport)
27 #define RESP_RESPONSE(pinfo) ((pinfo)->match_uint == (pinfo)->srcport)
28 #define DESEGMENT_ENABLED(pinfo) ((pinfo)->can_desegment && resp_desegment)
30 static dissector_handle_t resp_handle
;
31 static bool resp_desegment
= true;
33 static int proto_resp
;
36 static int ett_resp_bulk_string
;
37 static int ett_resp_array
;
39 static expert_field ei_resp_partial
;
40 static expert_field ei_resp_malformed_length
;
41 static expert_field ei_resp_array_recursion_too_deep
;
42 static expert_field ei_resp_reassembled_in_next_frame
;
44 static int hf_resp_string
;
45 static int hf_resp_error
;
46 static int hf_resp_bulk_string
;
47 static int hf_resp_bulk_string_length
;
48 static int hf_resp_bulk_string_value
;
49 static int hf_resp_integer
;
50 static int hf_resp_array
;
51 static int hf_resp_array_length
;
52 static int hf_resp_fragment
;
54 static int dissect_resp_loop(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int array_depth
, int64_t expected_elements
);
55 static void resp_bulk_string_enhance_colinfo_ascii(packet_info
*pinfo
, int array_depth
, int bulk_string_length
, const uint8_t *bulk_string_as_str
);
56 void proto_reg_handoff_resp(void);
57 void proto_register_resp(void);
59 static int dissect_resp_string(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int string_lenth
, int array_depth
) {
60 uint8_t *string_value
;
62 string_value
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
+ RESP_TOKEN_PREFIX_LENGTH
,
63 string_lenth
- RESP_TOKEN_PREFIX_LENGTH
, ENC_ASCII
);
64 proto_tree_add_string(tree
, hf_resp_string
, tvb
, offset
, string_lenth
+ CRLF_LENGTH
, string_value
);
66 /* Simple strings can be used as a response for commands */
67 if (RESP_RESPONSE(pinfo
) && array_depth
== 0) {
68 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, " ", string_value
);
71 return string_lenth
+ CRLF_LENGTH
;
74 static int dissect_resp_error(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int string_lenth
) {
77 error_value
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
+ RESP_TOKEN_PREFIX_LENGTH
,
78 string_lenth
- RESP_TOKEN_PREFIX_LENGTH
, ENC_ASCII
);
79 proto_tree_add_string(tree
, hf_resp_error
, tvb
, offset
, string_lenth
+ CRLF_LENGTH
, error_value
);
80 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Error: %s", error_value
);
81 return string_lenth
+ CRLF_LENGTH
;
84 static int dissect_resp_bulk_string(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int bulk_string_string_length
, int array_depth
) {
85 uint8_t *bulk_string_length_as_str
;
86 int bulk_string_length
;
87 int bulk_string_captured_length
;
88 int bulk_string_captured_length_with_crlf
;
89 proto_item
*resp_string_item
;
90 proto_tree
*resp_string_tree
;
92 bulk_string_length_as_str
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
+ RESP_TOKEN_PREFIX_LENGTH
,
93 bulk_string_string_length
- RESP_TOKEN_PREFIX_LENGTH
, ENC_ASCII
);
94 bulk_string_length
= (int)g_ascii_strtoll(bulk_string_length_as_str
, NULL
, 10);
95 /* Negative string lengths */
96 if (bulk_string_length
< 0) {
98 resp_string_item
= proto_tree_add_item(tree
, hf_resp_bulk_string
, tvb
, offset
,bulk_string_string_length
+ CRLF_LENGTH
, ENC_NA
);
99 resp_string_tree
= proto_item_add_subtree(resp_string_item
, ett_resp_bulk_string
);
100 proto_tree_add_int(resp_string_tree
, hf_resp_bulk_string_length
, tvb
, offset
, bulk_string_string_length
+ CRLF_LENGTH
, bulk_string_length
);
101 if (bulk_string_length
== RESP_NULL_STRING
) {
102 proto_item_append_text(resp_string_item
, ": [NULL]");
104 expert_add_info(pinfo
, resp_string_item
, &ei_resp_malformed_length
);
106 return bulk_string_string_length
+ CRLF_LENGTH
;
109 /* We have either a bulk string or an empty string */
110 int remaining_bytes_for_bulkstring
= tvb_captured_length_remaining(tvb
, offset
+ bulk_string_string_length
+ CRLF_LENGTH
);
111 /* Do we have enough bytes in the tvb for what was reported in the string length? */
112 int is_fragmented
= remaining_bytes_for_bulkstring
< bulk_string_length
+ CRLF_LENGTH
;
114 if (DESEGMENT_ENABLED(pinfo
)) {
115 /* Desegment at the start of the bulk string instead of part way through */
116 pinfo
->desegment_offset
= offset
;
117 /* We know how many bytes we will need */
118 pinfo
->desegment_len
= bulk_string_length
+ CRLF_LENGTH
- remaining_bytes_for_bulkstring
;
121 /* There's no CRLF, we didn't get all the bytes needed */
122 bulk_string_captured_length
= remaining_bytes_for_bulkstring
;
123 bulk_string_captured_length_with_crlf
= remaining_bytes_for_bulkstring
;
124 col_append_str(pinfo
->cinfo
, COL_INFO
, " [partial]");
126 bulk_string_captured_length
= bulk_string_length
;
127 bulk_string_captured_length_with_crlf
= bulk_string_length
+ CRLF_LENGTH
;
130 /* Add protocol items */
131 resp_string_item
= proto_tree_add_item(tree
, hf_resp_bulk_string
, tvb
, offset
,
132 bulk_string_string_length
+ CRLF_LENGTH
+ bulk_string_captured_length_with_crlf
,ENC_NA
);
134 expert_add_info(pinfo
, resp_string_item
, &ei_resp_partial
);
136 resp_string_tree
= proto_item_add_subtree(resp_string_item
, ett_resp_bulk_string
);
137 proto_tree_add_int(resp_string_tree
, hf_resp_bulk_string_length
, tvb
, offset
, bulk_string_string_length
+ CRLF_LENGTH
, bulk_string_length
);
138 offset
+= bulk_string_string_length
+ CRLF_LENGTH
;
139 if (bulk_string_captured_length
> 0) {
140 proto_tree_add_item(resp_string_tree
, hf_resp_bulk_string_value
, tvb
, offset
, bulk_string_captured_length
, ENC_NA
);
143 /* Enhance display */
144 uint8_t *bulk_string_as_str
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
, bulk_string_captured_length
, ENC_NA
);
145 if (g_str_is_ascii(bulk_string_as_str
)) {
146 proto_item_append_text(resp_string_item
, ": %s", bulk_string_as_str
);
147 resp_bulk_string_enhance_colinfo_ascii(pinfo
, array_depth
, bulk_string_length
, bulk_string_as_str
);
148 } else if(array_depth
== 0) {
149 /* Otherwise, just append that we captured bulk strings (and only do so if they aren't part of an array */
150 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " BulkString(%d)", bulk_string_length
);
153 return bulk_string_string_length
+ CRLF_LENGTH
+ bulk_string_captured_length_with_crlf
;
156 static void resp_bulk_string_enhance_colinfo_ascii(packet_info
*pinfo
, int array_depth
, int bulk_string_length
, const uint8_t *bulk_string_as_str
) {
157 /* Request commands are arrays */
158 if (RESP_REQUEST(pinfo
) && array_depth
== 1) {
159 if (bulk_string_length
< BULK_STRING_MAX_DISPLAY
) {
160 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, " ", bulk_string_as_str
);
162 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " BulkString(%d)", bulk_string_length
);
166 /* Print responses with zero depth */
167 if (RESP_RESPONSE(pinfo
) && array_depth
== 0 && bulk_string_length
< BULK_STRING_MAX_DISPLAY
) {
168 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, " ", bulk_string_as_str
);
171 /* Otherwise, just display that there is a bulk string in the response (for top-level) */
172 if (array_depth
== 0) {
173 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " BulkString(%d)", bulk_string_length
);
177 static int dissect_resp_integer(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int bulk_string_string_length
, int array_depth
) {
178 uint8_t *integer_as_string
;
180 integer_as_string
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
+ RESP_TOKEN_PREFIX_LENGTH
,
181 bulk_string_string_length
- RESP_TOKEN_PREFIX_LENGTH
, ENC_ASCII
);
182 integer
= g_ascii_strtoll(integer_as_string
, NULL
, 10);
183 proto_tree_add_int64(tree
, hf_resp_integer
, tvb
, offset
, bulk_string_string_length
+ CRLF_LENGTH
, integer
);
184 /* Simple integers can be used as a response for commands */
185 if (RESP_RESPONSE(pinfo
) && array_depth
== 0) {
186 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " %" PRId64
, integer
);
188 return bulk_string_string_length
+ CRLF_LENGTH
;
191 // NOLINTNEXTLINE(misc-no-recursion)
192 static int dissect_resp_array(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int string_length
, int array_depth
) {
193 uint8_t *array_length_as_string
= tvb_get_string_enc(pinfo
->pool
, tvb
, offset
+ RESP_TOKEN_PREFIX_LENGTH
,
194 string_length
- RESP_TOKEN_PREFIX_LENGTH
, ENC_ASCII
);
195 int64_t array_length
= g_ascii_strtoll(array_length_as_string
, NULL
, 10);
197 /* We'll fix up the length later when we know how long it actually was */
198 proto_item
*array_item
= proto_tree_add_item(tree
, hf_resp_array
, tvb
, offset
, string_length
+ CRLF_LENGTH
, ENC_NA
);
200 proto_tree
*array_tree
= proto_item_add_subtree(array_item
, ett_resp_array
);
201 proto_tree_add_int64(array_tree
, hf_resp_array_length
, tvb
, offset
, string_length
+ CRLF_LENGTH
, array_length
);
203 /* Null array, no elements */
204 if (array_length
<= 0) {
205 switch (array_length
) {
206 case RESP_NULL_ARRAY
:
207 proto_item_append_text(array_item
, ": NULL");
210 proto_item_append_text(array_item
, ": Empty");
213 expert_add_info(pinfo
, array_item
, &ei_resp_malformed_length
);
216 return string_length
+ CRLF_LENGTH
;
219 proto_item_append_text(array_item
, ": Length %" PRId64
, array_length
);
221 /* Bail out if we're recursing too much */
222 if (array_depth
> MAX_ARRAY_DEPTH_TO_RECURSE
) {
223 expert_add_info(pinfo
, array_item
, &ei_resp_array_recursion_too_deep
);
224 return string_length
+ CRLF_LENGTH
;
227 /* Non-empty array, but we've ran out of bytes in the tvb */
228 if (!tvb_offset_exists(tvb
, offset
+ string_length
+ CRLF_LENGTH
) && array_length
> 0) {
229 if (DESEGMENT_ENABLED(pinfo
)) {
230 pinfo
->desegment_offset
= offset
;
231 pinfo
->desegment_len
= DESEGMENT_ONE_MORE_SEGMENT
;
234 expert_add_info(pinfo
, array_item
, &ei_resp_partial
);
237 /* Add to the info column for responses. Don't do this for requests which are typically commands.
238 * These are extracted in the bulk string dissector */
239 if (RESP_RESPONSE(pinfo
) && array_depth
== 0) {
240 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Array(%" PRId64
")" , array_length
);
244 int dissected_offset
= dissect_resp_loop(tvb
, pinfo
, array_tree
, offset
+ string_length
+ CRLF_LENGTH
,array_depth
+ 1, array_length
);
245 if (dissected_offset
== -1) {
246 /* Override any desegment lengths previously set as we want to start from the beginning of the array */
247 pinfo
->desegment_offset
= offset
;
248 pinfo
->desegment_len
= DESEGMENT_ONE_MORE_SEGMENT
;
249 /* We've partially decoded some of the array, but we've asked for all. It will still show in the proto tree so give an
250 * indication as to why it's only partially there*/
251 expert_add_info(pinfo
, array_item
, &ei_resp_reassembled_in_next_frame
);
254 proto_item_set_len(array_item
, dissected_offset
- offset
);
255 return dissected_offset
- offset
;
258 // NOLINTNEXTLINE(misc-no-recursion)
259 static int dissect_resp_message(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int string_length
, int array_depth
) {
260 switch (tvb_get_uint8(tvb
, offset
)) {
262 return dissect_resp_string(tvb
, pinfo
, tree
, offset
, string_length
, array_depth
);
264 return dissect_resp_error(tvb
, pinfo
, tree
, offset
, string_length
);
266 return dissect_resp_integer(tvb
, pinfo
, tree
, offset
, string_length
, array_depth
);
268 return dissect_resp_bulk_string(tvb
, pinfo
, tree
, offset
, string_length
, array_depth
);
270 return dissect_resp_array(tvb
, pinfo
, tree
, offset
, string_length
, array_depth
);
272 /* We have an erroneous \r\n if the string length is 0. */
273 if (string_length
== 0) {
277 * - A command we don't support yet (RESPv3 commands that aren't implemented yet)
278 * - Reassembly is disabled and data is between packet boundaries
279 * - It's the first frame in a partial capture
281 col_append_str(pinfo
->cinfo
, COL_INFO
, " [fragment]");
282 proto_tree_add_item(tree
, hf_resp_fragment
, tvb
, offset
, string_length
+ CRLF_LENGTH
,ENC_NA
);
283 return string_length
+ CRLF_LENGTH
;
287 // NOLINTNEXTLINE(misc-no-recursion)
288 static int dissect_resp_loop(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, int offset
, int array_depth
, int64_t expected_elements
) {
290 int crlf_string_line_length
;
291 int done_elements
= 0;
293 while (tvb_offset_exists(tvb
, offset
)) {
294 /* If we ended up in here from a recursive call when traversing an array, don't drain the tvb to empty.
295 * Only do the amount of elements that are expected in the array */
296 if (expected_elements
>= 0 && done_elements
== expected_elements
) {
299 crlf_string_line_length
= tvb_find_line_end(tvb
, offset
, -1, NULL
, DESEGMENT_ENABLED(pinfo
));
300 /* If desegment is disabled, tvb_find_line_end() will return a positive length, regardless if it finds a CRLF */
301 if (crlf_string_line_length
== -1) {
302 pinfo
->desegment_offset
= offset
;
303 pinfo
->desegment_len
= DESEGMENT_ONE_MORE_SEGMENT
;
306 increment_dissection_depth(pinfo
);
307 error_or_offset
= dissect_resp_message(tvb
, pinfo
, tree
, offset
, crlf_string_line_length
, array_depth
);
308 decrement_dissection_depth(pinfo
);
309 if (error_or_offset
== -1) {
313 offset
+= error_or_offset
;
319 static int dissect_resp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data _U_
) {
321 proto_item
*root_resp_item
;
322 proto_tree
*resp_tree
;
324 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "RESP");
325 col_clear(pinfo
->cinfo
, COL_INFO
);
326 col_append_str(pinfo
->cinfo
, COL_INFO
, RESP_RESPONSE(pinfo
) ? "Response:" : "Request:");
327 root_resp_item
= proto_tree_add_item(tree
, proto_resp
, tvb
, 0, -1, ENC_NA
);
328 resp_tree
= proto_item_add_subtree(root_resp_item
, ett_resp
);
330 int dissected_length
= dissect_resp_loop(tvb
, pinfo
, resp_tree
, offset
, 0, -1);
331 if (dissected_length
== -1) {
332 col_append_str(pinfo
->cinfo
, COL_INFO
, " [continuation]");
333 return tvb_captured_length(tvb
);
335 return dissected_length
;
339 void proto_register_resp(void) {
341 static hf_register_info hf
[] = {
343 { "String", "resp.string",
344 FT_STRING
, BASE_NONE
,
350 { "Error", "resp.error",
351 FT_STRING
, BASE_NONE
,
356 { &hf_resp_bulk_string
,
357 { "Bulk String", "resp.bulk_string",
363 { &hf_resp_bulk_string_value
,
364 { "Value", "resp.bulk_string.value",
370 { &hf_resp_bulk_string_length
,
371 { "Length", "resp.bulk_string.length",
378 { "Integer", "resp.integer",
385 { "Array", "resp.array",
391 { &hf_resp_array_length
,
392 { "Length", "resp.array.length",
399 { "Fragment", "resp.fragment",
408 static int *ett
[] = {
410 &ett_resp_bulk_string
,
414 static ei_register_info ei
[] = {
415 { &ei_resp_partial
, { "resp.partial", PI_UNDECODED
, PI_NOTE
, "Field is only partially decoded", EXPFILL
}},
416 { &ei_resp_malformed_length
, { "resp.malformed_length", PI_UNDECODED
, PI_ERROR
, "Malformed length specified", EXPFILL
}},
417 { &ei_resp_reassembled_in_next_frame
, {"resp.reassembled_in_next_frame", PI_UNDECODED
, PI_NOTE
,
418 "Array is partially decoded. Re-assembled array is in the next frame", EXPFILL
}},
419 { &ei_resp_array_recursion_too_deep
, { "resp.array_recursion_too_deep", PI_UNDECODED
, PI_NOTE
,
420 "Array is too deep to recurse any further. Subsequent elements attached to the protocol "
421 "tree may not reflect their actual location in the array", EXPFILL
}},
424 proto_resp
= proto_register_protocol("REdis Serialization Protocol", "RESP", "resp");
425 module_t
*resp_module
= prefs_register_protocol(proto_resp
, NULL
);
426 prefs_register_bool_preference(resp_module
, "desegment_data",
427 "Reassemble RESP data spanning multiple TCP segments",
428 "Whether the RESP dissector should reassemble command and response lines"
429 " spanning multiple TCP segments. To use this option, you must also enable "
430 "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
433 expert_module_t
*expert_pcp
= expert_register_protocol(proto_resp
);
434 expert_register_field_array(expert_pcp
, ei
, array_length(ei
));
436 resp_handle
= register_dissector("resp", dissect_resp
, proto_resp
);
438 proto_register_field_array(proto_resp
, hf
, array_length(hf
));
439 proto_register_subtree_array(ett
, array_length(ett
));
443 void proto_reg_handoff_resp(void) {
444 dissector_add_uint_with_preference("tcp.port", RESP_PORT
, resp_handle
);