Home | History | Annotate | Line # | Download | only in dist
print-resp.c revision 1.1.1.1
      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 (at) 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 }
    376