]> The Tcpdump Group git mirrors - tcpdump/blob - print-resp.c
Add a summary comment in all other printers
[tcpdump] / print-resp.c
1 /*
2 * Copyright (c) 2015 The TCPDUMP project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
17 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
18 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 *
27 * Initial contribution by Andrew Darqui (andrew.darqui@gmail.com).
28 */
29
30 /* \summary: REdis Serialization Protocol (RESP) printer */
31
32 #define NETDISSECT_REWORKED
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36
37 #include <netdissect-stdinc.h>
38 #include "netdissect.h"
39 #include <limits.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <errno.h>
43
44 #include "extract.h"
45
46 static const char tstr[] = " [|RESP]";
47
48 /*
49 * For information regarding RESP, see: http://redis.io/topics/protocol
50 */
51
52 #define RESP_SIMPLE_STRING '+'
53 #define RESP_ERROR '-'
54 #define RESP_INTEGER ':'
55 #define RESP_BULK_STRING '$'
56 #define RESP_ARRAY '*'
57
58 #define resp_print_empty(ndo) ND_PRINT((ndo, " empty"))
59 #define resp_print_null(ndo) ND_PRINT((ndo, " null"))
60 #define resp_print_invalid(ndo) ND_PRINT((ndo, " invalid"))
61
62 void resp_print(netdissect_options *, const u_char *, u_int);
63 static int resp_parse(netdissect_options *, register const u_char *, int);
64 static int resp_print_string_error_integer(netdissect_options *, register const u_char *, int);
65 static int resp_print_simple_string(netdissect_options *, register const u_char *, int);
66 static int resp_print_integer(netdissect_options *, register const u_char *, int);
67 static int resp_print_error(netdissect_options *, register const u_char *, int);
68 static int resp_print_bulk_string(netdissect_options *, register const u_char *, int);
69 static int resp_print_bulk_array(netdissect_options *, register const u_char *, int);
70 static int resp_print_inline(netdissect_options *, register const u_char *, int);
71
72 /*
73 * MOVE_FORWARD:
74 * Attempts to move our 'ptr' forward until a \r\n is found,
75 * while also making sure we don't exceed the buffer 'len'.
76 * If we exceed, jump to trunc.
77 */
78 #define MOVE_FORWARD(ptr, len) \
79 while(*ptr != '\r' && *(ptr+1) != '\n') { ND_TCHECK2(*ptr, 2); ptr++; len--; }
80
81 /*
82 * MOVE_FORWARD_CR_OR_LF
83 * Attempts to move our 'ptr' forward until a \r or \n is found,
84 * while also making sure we don't exceed the buffer 'len'.
85 * If we exceed, jump to trunc.
86 */
87 #define MOVE_FORWARD_CR_OR_LF(ptr, len) \
88 while(*ptr != '\r' && *ptr != '\n') { ND_TCHECK(*ptr); ptr++; len--; }
89
90 /*
91 * CONSUME_CR_OR_LF
92 * Consume all consecutive \r and \n bytes.
93 * If we exceed 'len', jump to trunc.
94 */
95 #define CONSUME_CR_OR_LF(ptr, len) \
96 while (*ptr == '\r' || *ptr == '\n') { ND_TCHECK(*ptr); ptr++; len--; }
97
98 /*
99 * INCBY
100 * Attempts to increment our 'ptr' by 'increment' bytes.
101 * If our increment exceeds the buffer length (len - increment),
102 * bail out by jumping to the trunc goto tag.
103 */
104 #define INCBY(ptr, increment, len) \
105 { ND_TCHECK2(*ptr, increment); ptr+=increment; len-=increment; }
106
107 /*
108 * INC1
109 * Increment our ptr by 1 byte.
110 * Most often used to skip an opcode (+-:*$ etc)
111 */
112 #define INC1(ptr, len) INCBY(ptr, 1, len)
113
114 /*
115 * INC2
116 * Increment our ptr by 2 bytes.
117 * Most often used to skip CRLF (\r\n).
118 */
119 #define INC2(ptr, len) INCBY(ptr, 2, len)
120
121 /*
122 * TEST_RET_LEN
123 * If ret_len is < 0, jump to the trunc tag which returns (-1)
124 * and 'bubbles up' to printing tstr. Otherwise, return ret_len.
125 */
126 #define TEST_RET_LEN(rl) \
127 if (rl < 0) { goto trunc; } else { return rl; }
128
129 /*
130 * TEST_RET_LEN_NORETURN
131 * If ret_len is < 0, jump to the trunc tag which returns (-1)
132 * and 'bubbles up' to printing tstr. Otherwise, continue onward.
133 */
134 #define TEST_RET_LEN_NORETURN(rl) \
135 if (rl < 0) { goto trunc; }
136
137 /*
138 * RESP_PRINT_SEGMENT
139 * Prints a segment in the form of: ' "<stuff>"\n"
140 */
141 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \
142 ND_PRINT((_ndo, " \"")); \
143 if (fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \
144 goto trunc; \
145 fn_print_char(_ndo, '"');
146
147 void
148 resp_print(netdissect_options *ndo, const u_char *bp, u_int length)
149 {
150 int ret_len = 0, length_cur = length;
151
152 if(!bp || length <= 0)
153 return;
154
155 ND_PRINT((ndo, ": RESP"));
156 while (length_cur > 0) {
157 /*
158 * This block supports redis pipelining.
159 * For example, multiple operations can be pipelined within the same string:
160 * "*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n"
161 * or
162 * "PING\r\nPING\r\nPING\r\n"
163 * In order to handle this case, we must try and parse 'bp' until
164 * 'length' bytes have been processed or we reach a trunc condition.
165 */
166 ret_len = resp_parse(ndo, bp, length_cur);
167 TEST_RET_LEN_NORETURN(ret_len);
168 bp += ret_len;
169 length_cur -= ret_len;
170 }
171
172 return;
173
174 trunc:
175 ND_PRINT((ndo, "%s", tstr));
176 }
177
178 static int
179 resp_parse(netdissect_options *ndo, register const u_char *bp, int length)
180 {
181 int ret_len = 0;
182 u_char op = *bp;
183
184 ND_TCHECK(*bp);
185
186 switch(op) {
187 case RESP_SIMPLE_STRING: ret_len = resp_print_simple_string(ndo, bp, length); break;
188 case RESP_INTEGER: ret_len = resp_print_integer(ndo, bp, length); break;
189 case RESP_ERROR: ret_len = resp_print_error(ndo, bp, length); break;
190 case RESP_BULK_STRING: ret_len = resp_print_bulk_string(ndo, bp, length); break;
191 case RESP_ARRAY: ret_len = resp_print_bulk_array(ndo, bp, length); break;
192 default: ret_len = resp_print_inline(ndo, bp, length); break;
193 }
194
195 TEST_RET_LEN(ret_len);
196
197 trunc:
198 return (-1);
199 }
200
201 static int
202 resp_print_simple_string(netdissect_options *ndo, register const u_char *bp, int length) {
203 return resp_print_string_error_integer(ndo, bp, length);
204 }
205
206 static int
207 resp_print_integer(netdissect_options *ndo, register const u_char *bp, int length) {
208 return resp_print_string_error_integer(ndo, bp, length);
209 }
210
211 static int
212 resp_print_error(netdissect_options *ndo, register const u_char *bp, int length) {
213 return resp_print_string_error_integer(ndo, bp, length);
214 }
215
216 static int
217 resp_print_string_error_integer(netdissect_options *ndo, register const u_char *bp, int length) {
218 int length_cur = length, len, ret_len = 0;
219 const u_char *bp_ptr = bp;
220
221 /*
222 * MOVE_FORWARD moves past the string that follows the (+-;) opcodes
223 * +OK\r\n
224 * -ERR ...\r\n
225 * :02912309\r\n
226 */
227 MOVE_FORWARD(bp_ptr, length_cur);
228 len = (bp_ptr - bp);
229 ND_TCHECK2(*bp, len);
230 RESP_PRINT_SEGMENT(ndo, bp+1, len-1);
231 ret_len = len /*<1byte>+<string>*/ + 2 /*<CRLF>*/;
232
233 TEST_RET_LEN(ret_len);
234
235 trunc:
236 return (-1);
237 }
238
239 static int
240 resp_print_bulk_string(netdissect_options *ndo, register const u_char *bp, int length) {
241 int length_cur = length, string_len;
242 long strtol_ret;
243 char *p;
244
245 ND_TCHECK(*bp);
246
247 /* opcode: '$' */
248 INC1(bp, length_cur);
249 ND_TCHECK(*bp);
250
251 /* <length> */
252 errno = 0;
253 strtol_ret = strtol((const char *)bp, &p, 10);
254 if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
255 strtol_ret > INT_MAX)
256 string_len = -2; /* invalid */
257 else
258 string_len = (int)strtol_ret;
259
260 /* move to \r\n */
261 MOVE_FORWARD(bp, length_cur);
262
263 /* \r\n */
264 INC2(bp, length_cur);
265
266 if (string_len > 0) {
267 /* Byte string of length string_len */
268 ND_TCHECK2(*bp, string_len);
269 RESP_PRINT_SEGMENT(ndo, bp, string_len);
270 } else {
271 switch(string_len) {
272 case 0: resp_print_empty(ndo); break;
273 case (-1): {
274 /* This is the NULL response. It follows a different pattern: $-1\r\n */
275 resp_print_null(ndo);
276 TEST_RET_LEN(length - length_cur);
277 /* returned ret_len or jumped to trunc */
278 }
279 default: resp_print_invalid(ndo); break;
280 }
281 }
282
283 /* <string> */
284 INCBY(bp, string_len, length_cur);
285
286 /* \r\n */
287 INC2(bp, length_cur);
288
289 TEST_RET_LEN(length - length_cur);
290
291 trunc:
292 return (-1);
293 }
294
295 static int
296 resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int length) {
297 int length_cur = length, array_len, i, ret_len = 0;
298 long strtol_ret;
299 char *p;
300
301 ND_TCHECK(*bp);
302
303 /* opcode: '*' */
304 INC1(bp, length_cur);
305 ND_TCHECK(*bp);
306
307 /* <array_length> */
308 errno = 0;
309 strtol_ret = strtol((const char *)bp, &p, 10);
310 if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
311 strtol_ret > INT_MAX)
312 array_len = -2; /* invalid */
313 else
314 array_len = (int)strtol_ret;
315
316 /* move to \r\n */
317 MOVE_FORWARD(bp, length_cur);
318
319 /* \r\n */
320 INC2(bp, length_cur);
321
322 if (array_len > 0) {
323 /* non empty array */
324 for (i = 0; i < array_len; i++) {
325 ret_len = resp_parse(ndo, bp, length_cur);
326
327 TEST_RET_LEN_NORETURN(ret_len);
328
329 bp += ret_len;
330 length_cur -= ret_len;
331
332 TEST_RET_LEN_NORETURN(length - length_cur);
333 }
334 } else {
335 /* empty, null, or invalid */
336 switch(array_len) {
337 case 0: resp_print_empty(ndo); break;
338 case (-1): resp_print_null(ndo); break;
339 default: resp_print_invalid(ndo); break;
340 }
341 }
342
343 TEST_RET_LEN(length - length_cur);
344
345 trunc:
346 return (-1);
347 }
348
349 static int
350 resp_print_inline(netdissect_options *ndo, register const u_char *bp, int length) {
351 int length_cur = length, len;
352 const u_char *bp_ptr;
353
354 /*
355 * Inline commands are simply 'strings' followed by \r or \n or both.
356 * Redis will do it's best to split/parse these strings.
357 * This feature of redis is implemented to support the ability of
358 * command parsing from telnet/nc sessions etc.
359 *
360 * <string><\r||\n||\r\n...>
361 */
362 CONSUME_CR_OR_LF(bp, length_cur);
363 bp_ptr = bp;
364 MOVE_FORWARD_CR_OR_LF(bp_ptr, length_cur);
365 len = (bp_ptr - bp);
366 ND_TCHECK2(*bp, len);
367 RESP_PRINT_SEGMENT(ndo, bp, len);
368 CONSUME_CR_OR_LF(bp_ptr, length_cur);
369
370 TEST_RET_LEN(length - length_cur);
371
372 trunc:
373 return (-1);
374 }