]> The Tcpdump Group git mirrors - tcpdump/blob - print-resp.c
Remove NETDISSECT_REWORKED macro
[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 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35
36 #include <netdissect-stdinc.h>
37 #include "netdissect.h"
38 #include <limits.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <errno.h>
42
43 #include "extract.h"
44
45 static const char tstr[] = " [|RESP]";
46
47 /*
48 * For information regarding RESP, see: http://redis.io/topics/protocol
49 */
50
51 #define RESP_SIMPLE_STRING '+'
52 #define RESP_ERROR '-'
53 #define RESP_INTEGER ':'
54 #define RESP_BULK_STRING '$'
55 #define RESP_ARRAY '*'
56
57 #define resp_print_empty(ndo) ND_PRINT((ndo, " empty"))
58 #define resp_print_null(ndo) ND_PRINT((ndo, " null"))
59 #define resp_print_invalid(ndo) ND_PRINT((ndo, " invalid"))
60
61 void resp_print(netdissect_options *, const u_char *, u_int);
62 static int resp_parse(netdissect_options *, register const u_char *, int);
63 static int resp_print_string_error_integer(netdissect_options *, register const u_char *, int);
64 static int resp_print_simple_string(netdissect_options *, register const u_char *, int);
65 static int resp_print_integer(netdissect_options *, register const u_char *, int);
66 static int resp_print_error(netdissect_options *, register const u_char *, int);
67 static int resp_print_bulk_string(netdissect_options *, register const u_char *, int);
68 static int resp_print_bulk_array(netdissect_options *, register const u_char *, int);
69 static int resp_print_inline(netdissect_options *, register const u_char *, int);
70
71 /*
72 * MOVE_FORWARD:
73 * Attempts to move our 'ptr' forward until a \r\n is found,
74 * while also making sure we don't exceed the buffer 'len'.
75 * If we exceed, jump to trunc.
76 */
77 #define MOVE_FORWARD(ptr, len) \
78 while(*ptr != '\r' && *(ptr+1) != '\n') { ND_TCHECK2(*ptr, 2); ptr++; len--; }
79
80 /*
81 * MOVE_FORWARD_CR_OR_LF
82 * Attempts to move our 'ptr' forward until a \r or \n is found,
83 * while also making sure we don't exceed the buffer 'len'.
84 * If we exceed, jump to trunc.
85 */
86 #define MOVE_FORWARD_CR_OR_LF(ptr, len) \
87 while(*ptr != '\r' && *ptr != '\n') { ND_TCHECK(*ptr); ptr++; len--; }
88
89 /*
90 * CONSUME_CR_OR_LF
91 * Consume all consecutive \r and \n bytes.
92 * If we exceed 'len', jump to trunc.
93 */
94 #define CONSUME_CR_OR_LF(ptr, len) \
95 while (*ptr == '\r' || *ptr == '\n') { ND_TCHECK(*ptr); ptr++; len--; }
96
97 /*
98 * INCBY
99 * Attempts to increment our 'ptr' by 'increment' bytes.
100 * If our increment exceeds the buffer length (len - increment),
101 * bail out by jumping to the trunc goto tag.
102 */
103 #define INCBY(ptr, increment, len) \
104 { ND_TCHECK2(*ptr, increment); ptr+=increment; len-=increment; }
105
106 /*
107 * INC1
108 * Increment our ptr by 1 byte.
109 * Most often used to skip an opcode (+-:*$ etc)
110 */
111 #define INC1(ptr, len) INCBY(ptr, 1, len)
112
113 /*
114 * INC2
115 * Increment our ptr by 2 bytes.
116 * Most often used to skip CRLF (\r\n).
117 */
118 #define INC2(ptr, len) INCBY(ptr, 2, len)
119
120 /*
121 * TEST_RET_LEN
122 * If ret_len is < 0, jump to the trunc tag which returns (-1)
123 * and 'bubbles up' to printing tstr. Otherwise, return ret_len.
124 */
125 #define TEST_RET_LEN(rl) \
126 if (rl < 0) { goto trunc; } else { return rl; }
127
128 /*
129 * TEST_RET_LEN_NORETURN
130 * If ret_len is < 0, jump to the trunc tag which returns (-1)
131 * and 'bubbles up' to printing tstr. Otherwise, continue onward.
132 */
133 #define TEST_RET_LEN_NORETURN(rl) \
134 if (rl < 0) { goto trunc; }
135
136 /*
137 * RESP_PRINT_SEGMENT
138 * Prints a segment in the form of: ' "<stuff>"\n"
139 */
140 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \
141 ND_PRINT((_ndo, " \"")); \
142 if (fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \
143 goto trunc; \
144 fn_print_char(_ndo, '"');
145
146 void
147 resp_print(netdissect_options *ndo, const u_char *bp, u_int length)
148 {
149 int ret_len = 0, length_cur = length;
150
151 if(!bp || length <= 0)
152 return;
153
154 ND_PRINT((ndo, ": RESP"));
155 while (length_cur > 0) {
156 /*
157 * This block supports redis pipelining.
158 * For example, multiple operations can be pipelined within the same string:
159 * "*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"
160 * or
161 * "PING\r\nPING\r\nPING\r\n"
162 * In order to handle this case, we must try and parse 'bp' until
163 * 'length' bytes have been processed or we reach a trunc condition.
164 */
165 ret_len = resp_parse(ndo, bp, length_cur);
166 TEST_RET_LEN_NORETURN(ret_len);
167 bp += ret_len;
168 length_cur -= ret_len;
169 }
170
171 return;
172
173 trunc:
174 ND_PRINT((ndo, "%s", tstr));
175 }
176
177 static int
178 resp_parse(netdissect_options *ndo, register const u_char *bp, int length)
179 {
180 int ret_len = 0;
181 u_char op = *bp;
182
183 ND_TCHECK(*bp);
184
185 switch(op) {
186 case RESP_SIMPLE_STRING: ret_len = resp_print_simple_string(ndo, bp, length); break;
187 case RESP_INTEGER: ret_len = resp_print_integer(ndo, bp, length); break;
188 case RESP_ERROR: ret_len = resp_print_error(ndo, bp, length); break;
189 case RESP_BULK_STRING: ret_len = resp_print_bulk_string(ndo, bp, length); break;
190 case RESP_ARRAY: ret_len = resp_print_bulk_array(ndo, bp, length); break;
191 default: ret_len = resp_print_inline(ndo, bp, length); break;
192 }
193
194 TEST_RET_LEN(ret_len);
195
196 trunc:
197 return (-1);
198 }
199
200 static int
201 resp_print_simple_string(netdissect_options *ndo, register const u_char *bp, int length) {
202 return resp_print_string_error_integer(ndo, bp, length);
203 }
204
205 static int
206 resp_print_integer(netdissect_options *ndo, register const u_char *bp, int length) {
207 return resp_print_string_error_integer(ndo, bp, length);
208 }
209
210 static int
211 resp_print_error(netdissect_options *ndo, register const u_char *bp, int length) {
212 return resp_print_string_error_integer(ndo, bp, length);
213 }
214
215 static int
216 resp_print_string_error_integer(netdissect_options *ndo, register const u_char *bp, int length) {
217 int length_cur = length, len, ret_len = 0;
218 const u_char *bp_ptr = bp;
219
220 /*
221 * MOVE_FORWARD moves past the string that follows the (+-;) opcodes
222 * +OK\r\n
223 * -ERR ...\r\n
224 * :02912309\r\n
225 */
226 MOVE_FORWARD(bp_ptr, length_cur);
227 len = (bp_ptr - bp);
228 ND_TCHECK2(*bp, len);
229 RESP_PRINT_SEGMENT(ndo, bp+1, len-1);
230 ret_len = len /*<1byte>+<string>*/ + 2 /*<CRLF>*/;
231
232 TEST_RET_LEN(ret_len);
233
234 trunc:
235 return (-1);
236 }
237
238 static int
239 resp_print_bulk_string(netdissect_options *ndo, register const u_char *bp, int length) {
240 int length_cur = length, string_len;
241 long strtol_ret;
242 char *p;
243
244 ND_TCHECK(*bp);
245
246 /* opcode: '$' */
247 INC1(bp, length_cur);
248 ND_TCHECK(*bp);
249
250 /* <length> */
251 errno = 0;
252 strtol_ret = strtol((const char *)bp, &p, 10);
253 if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
254 strtol_ret > INT_MAX)
255 string_len = -2; /* invalid */
256 else
257 string_len = (int)strtol_ret;
258
259 /* move to \r\n */
260 MOVE_FORWARD(bp, length_cur);
261
262 /* \r\n */
263 INC2(bp, length_cur);
264
265 if (string_len > 0) {
266 /* Byte string of length string_len */
267 ND_TCHECK2(*bp, string_len);
268 RESP_PRINT_SEGMENT(ndo, bp, string_len);
269 } else {
270 switch(string_len) {
271 case 0: resp_print_empty(ndo); break;
272 case (-1): {
273 /* This is the NULL response. It follows a different pattern: $-1\r\n */
274 resp_print_null(ndo);
275 TEST_RET_LEN(length - length_cur);
276 /* returned ret_len or jumped to trunc */
277 }
278 default: resp_print_invalid(ndo); break;
279 }
280 }
281
282 /* <string> */
283 INCBY(bp, string_len, length_cur);
284
285 /* \r\n */
286 INC2(bp, length_cur);
287
288 TEST_RET_LEN(length - length_cur);
289
290 trunc:
291 return (-1);
292 }
293
294 static int
295 resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int length) {
296 int length_cur = length, array_len, i, ret_len = 0;
297 long strtol_ret;
298 char *p;
299
300 ND_TCHECK(*bp);
301
302 /* opcode: '*' */
303 INC1(bp, length_cur);
304 ND_TCHECK(*bp);
305
306 /* <array_length> */
307 errno = 0;
308 strtol_ret = strtol((const char *)bp, &p, 10);
309 if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
310 strtol_ret > INT_MAX)
311 array_len = -2; /* invalid */
312 else
313 array_len = (int)strtol_ret;
314
315 /* move to \r\n */
316 MOVE_FORWARD(bp, length_cur);
317
318 /* \r\n */
319 INC2(bp, length_cur);
320
321 if (array_len > 0) {
322 /* non empty array */
323 for (i = 0; i < array_len; i++) {
324 ret_len = resp_parse(ndo, bp, length_cur);
325
326 TEST_RET_LEN_NORETURN(ret_len);
327
328 bp += ret_len;
329 length_cur -= ret_len;
330
331 TEST_RET_LEN_NORETURN(length - length_cur);
332 }
333 } else {
334 /* empty, null, or invalid */
335 switch(array_len) {
336 case 0: resp_print_empty(ndo); break;
337 case (-1): resp_print_null(ndo); break;
338 default: resp_print_invalid(ndo); break;
339 }
340 }
341
342 TEST_RET_LEN(length - length_cur);
343
344 trunc:
345 return (-1);
346 }
347
348 static int
349 resp_print_inline(netdissect_options *ndo, register const u_char *bp, int length) {
350 int length_cur = length, len;
351 const u_char *bp_ptr;
352
353 /*
354 * Inline commands are simply 'strings' followed by \r or \n or both.
355 * Redis will do it's best to split/parse these strings.
356 * This feature of redis is implemented to support the ability of
357 * command parsing from telnet/nc sessions etc.
358 *
359 * <string><\r||\n||\r\n...>
360 */
361 CONSUME_CR_OR_LF(bp, length_cur);
362 bp_ptr = bp;
363 MOVE_FORWARD_CR_OR_LF(bp_ptr, length_cur);
364 len = (bp_ptr - bp);
365 ND_TCHECK2(*bp, len);
366 RESP_PRINT_SEGMENT(ndo, bp, len);
367 CONSUME_CR_OR_LF(bp_ptr, length_cur);
368
369 TEST_RET_LEN(length - length_cur);
370
371 trunc:
372 return (-1);
373 }