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