diff options
Diffstat (limited to 'src/print-resp.c')
| -rw-r--r-- | src/print-resp.c | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/src/print-resp.c b/src/print-resp.c new file mode 100644 index 0000000..5e36eda --- /dev/null +++ b/src/print-resp.c @@ -0,0 +1,375 @@ +/* + * This file implements decoding of the REdis Serialization Protocol. + * + * + * Copyright (c) 2015 The TCPDUMP project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Initial contribution by Andrew Darqui ([email protected]). + */ + +#define NETDISSECT_REWORKED +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <netdissect-stdinc.h> +#include "netdissect.h" +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include "extract.h" + +static const char tstr[] = " [|RESP]"; + +/* + * For information regarding RESP, see: http://redis.io/topics/protocol + */ + +#define RESP_SIMPLE_STRING '+' +#define RESP_ERROR '-' +#define RESP_INTEGER ':' +#define RESP_BULK_STRING '$' +#define RESP_ARRAY '*' + +#define resp_print_empty(ndo) ND_PRINT((ndo, " empty")) +#define resp_print_null(ndo) ND_PRINT((ndo, " null")) +#define resp_print_invalid(ndo) ND_PRINT((ndo, " invalid")) + +void resp_print(netdissect_options *, const u_char *, u_int); +static int resp_parse(netdissect_options *, register const u_char *, int); +static int resp_print_string_error_integer(netdissect_options *, register const u_char *, int); +static int resp_print_simple_string(netdissect_options *, register const u_char *, int); +static int resp_print_integer(netdissect_options *, register const u_char *, int); +static int resp_print_error(netdissect_options *, register const u_char *, int); +static int resp_print_bulk_string(netdissect_options *, register const u_char *, int); +static int resp_print_bulk_array(netdissect_options *, register const u_char *, int); +static int resp_print_inline(netdissect_options *, register const u_char *, int); + +/* + * MOVE_FORWARD: + * Attempts to move our 'ptr' forward until a \r\n is found, + * while also making sure we don't exceed the buffer 'len'. + * If we exceed, jump to trunc. + */ +#define MOVE_FORWARD(ptr, len) \ + while(*ptr != '\r' && *(ptr+1) != '\n') { ND_TCHECK2(*ptr, 2); ptr++; len--; } + +/* + * MOVE_FORWARD_CR_OR_LF + * Attempts to move our 'ptr' forward until a \r or \n is found, + * while also making sure we don't exceed the buffer 'len'. + * If we exceed, jump to trunc. + */ +#define MOVE_FORWARD_CR_OR_LF(ptr, len) \ + while(*ptr != '\r' && *ptr != '\n') { ND_TCHECK(*ptr); ptr++; len--; } + +/* + * CONSUME_CR_OR_LF + * Consume all consecutive \r and \n bytes. + * If we exceed 'len', jump to trunc. + */ +#define CONSUME_CR_OR_LF(ptr, len) \ + while (*ptr == '\r' || *ptr == '\n') { ND_TCHECK(*ptr); ptr++; len--; } + +/* + * INCBY + * Attempts to increment our 'ptr' by 'increment' bytes. + * If our increment exceeds the buffer length (len - increment), + * bail out by jumping to the trunc goto tag. + */ +#define INCBY(ptr, increment, len) \ + { ND_TCHECK2(*ptr, increment); ptr+=increment; len-=increment; } + +/* + * INC1 + * Increment our ptr by 1 byte. + * Most often used to skip an opcode (+-:*$ etc) + */ +#define INC1(ptr, len) INCBY(ptr, 1, len) + +/* + * INC2 + * Increment our ptr by 2 bytes. + * Most often used to skip CRLF (\r\n). + */ +#define INC2(ptr, len) INCBY(ptr, 2, len) + +/* + * TEST_RET_LEN + * If ret_len is < 0, jump to the trunc tag which returns (-1) + * and 'bubbles up' to printing tstr. Otherwise, return ret_len. + */ +#define TEST_RET_LEN(rl) \ + if (rl < 0) { goto trunc; } else { return rl; } + +/* + * TEST_RET_LEN_NORETURN + * If ret_len is < 0, jump to the trunc tag which returns (-1) + * and 'bubbles up' to printing tstr. Otherwise, continue onward. + */ +#define TEST_RET_LEN_NORETURN(rl) \ + if (rl < 0) { goto trunc; } + +/* + * RESP_PRINT_SEGMENT + * Prints a segment in the form of: ' "<stuff>"\n" + */ +#define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \ + ND_PRINT((_ndo, " \"")); \ + if (fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \ + goto trunc; \ + fn_print_char(_ndo, '"'); + +void +resp_print(netdissect_options *ndo, const u_char *bp, u_int length) +{ + int ret_len = 0, length_cur = length; + + if(!bp || length <= 0) + return; + + ND_PRINT((ndo, ": RESP")); + while (length_cur > 0) { + /* + * This block supports redis pipelining. + * For example, multiple operations can be pipelined within the same string: + * "*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" + * or + * "PING\r\nPING\r\nPING\r\n" + * In order to handle this case, we must try and parse 'bp' until + * 'length' bytes have been processed or we reach a trunc condition. + */ + ret_len = resp_parse(ndo, bp, length_cur); + TEST_RET_LEN_NORETURN(ret_len); + bp += ret_len; + length_cur -= ret_len; + } + + return; + +trunc: + ND_PRINT((ndo, "%s", tstr)); +} + +static int +resp_parse(netdissect_options *ndo, register const u_char *bp, int length) +{ + int ret_len = 0; + u_char op = *bp; + + ND_TCHECK(*bp); + + switch(op) { + case RESP_SIMPLE_STRING: ret_len = resp_print_simple_string(ndo, bp, length); break; + case RESP_INTEGER: ret_len = resp_print_integer(ndo, bp, length); break; + case RESP_ERROR: ret_len = resp_print_error(ndo, bp, length); break; + case RESP_BULK_STRING: ret_len = resp_print_bulk_string(ndo, bp, length); break; + case RESP_ARRAY: ret_len = resp_print_bulk_array(ndo, bp, length); break; + default: ret_len = resp_print_inline(ndo, bp, length); break; + } + + TEST_RET_LEN(ret_len); + +trunc: + return (-1); +} + +static int +resp_print_simple_string(netdissect_options *ndo, register const u_char *bp, int length) { + return resp_print_string_error_integer(ndo, bp, length); +} + +static int +resp_print_integer(netdissect_options *ndo, register const u_char *bp, int length) { + return resp_print_string_error_integer(ndo, bp, length); +} + +static int +resp_print_error(netdissect_options *ndo, register const u_char *bp, int length) { + return resp_print_string_error_integer(ndo, bp, length); +} + +static int +resp_print_string_error_integer(netdissect_options *ndo, register const u_char *bp, int length) { + int length_cur = length, len, ret_len = 0; + const u_char *bp_ptr = bp; + + /* + * MOVE_FORWARD moves past the string that follows the (+-;) opcodes + * +OK\r\n + * -ERR ...\r\n + * :02912309\r\n + */ + MOVE_FORWARD(bp_ptr, length_cur); + len = (bp_ptr - bp); + ND_TCHECK2(*bp, len); + RESP_PRINT_SEGMENT(ndo, bp+1, len-1); + ret_len = len /*<1byte>+<string>*/ + 2 /*<CRLF>*/; + + TEST_RET_LEN(ret_len); + +trunc: + return (-1); +} + +static int +resp_print_bulk_string(netdissect_options *ndo, register const u_char *bp, int length) { + int length_cur = length, string_len; + long strtol_ret; + char *p; + + ND_TCHECK(*bp); + + /* opcode: '$' */ + INC1(bp, length_cur); + ND_TCHECK(*bp); + + /* <length> */ + errno = 0; + strtol_ret = strtol((const char *)bp, &p, 10); + if (errno != 0 || p == (const char *)bp || strtol_ret < -1 || + strtol_ret > INT_MAX) + string_len = -2; /* invalid */ + else + string_len = (int)strtol_ret; + + /* move to \r\n */ + MOVE_FORWARD(bp, length_cur); + + /* \r\n */ + INC2(bp, length_cur); + + if (string_len > 0) { + /* Byte string of length string_len */ + ND_TCHECK2(*bp, string_len); + RESP_PRINT_SEGMENT(ndo, bp, string_len); + } else { + switch(string_len) { + case 0: resp_print_empty(ndo); break; + case (-1): { + /* This is the NULL response. It follows a different pattern: $-1\r\n */ + resp_print_null(ndo); + TEST_RET_LEN(length - length_cur); + /* returned ret_len or jumped to trunc */ + } + default: resp_print_invalid(ndo); break; + } + } + + /* <string> */ + INCBY(bp, string_len, length_cur); + + /* \r\n */ + INC2(bp, length_cur); + + TEST_RET_LEN(length - length_cur); + +trunc: + return (-1); +} + +static int +resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int length) { + int length_cur = length, array_len, i, ret_len = 0; + long strtol_ret; + char *p; + + ND_TCHECK(*bp); + + /* opcode: '*' */ + INC1(bp, length_cur); + ND_TCHECK(*bp); + + /* <array_length> */ + errno = 0; + strtol_ret = strtol((const char *)bp, &p, 10); + if (errno != 0 || p == (const char *)bp || strtol_ret < -1 || + strtol_ret > INT_MAX) + array_len = -2; /* invalid */ + else + array_len = (int)strtol_ret; + + /* move to \r\n */ + MOVE_FORWARD(bp, length_cur); + + /* \r\n */ + INC2(bp, length_cur); + + if (array_len > 0) { + /* non empty array */ + for (i = 0; i < array_len; i++) { + ret_len = resp_parse(ndo, bp, length_cur); + + TEST_RET_LEN_NORETURN(ret_len); + + bp += ret_len; + length_cur -= ret_len; + + TEST_RET_LEN_NORETURN(length - length_cur); + } + } else { + /* empty, null, or invalid */ + switch(array_len) { + case 0: resp_print_empty(ndo); break; + case (-1): resp_print_null(ndo); break; + default: resp_print_invalid(ndo); break; + } + } + + TEST_RET_LEN(length - length_cur); + +trunc: + return (-1); +} + +static int +resp_print_inline(netdissect_options *ndo, register const u_char *bp, int length) { + int length_cur = length, len; + const u_char *bp_ptr; + + /* + * Inline commands are simply 'strings' followed by \r or \n or both. + * Redis will do it's best to split/parse these strings. + * This feature of redis is implemented to support the ability of + * command parsing from telnet/nc sessions etc. + * + * <string><\r||\n||\r\n...> + */ + CONSUME_CR_OR_LF(bp, length_cur); + bp_ptr = bp; + MOVE_FORWARD_CR_OR_LF(bp_ptr, length_cur); + len = (bp_ptr - bp); + ND_TCHECK2(*bp, len); + RESP_PRINT_SEGMENT(ndo, bp, len); + CONSUME_CR_OR_LF(bp_ptr, length_cur); + + TEST_RET_LEN(length - length_cur); + +trunc: + return (-1); +} |
