Home | History | Annotate | Line # | Download | only in dist
print-resp.c revision 1.1.1.5
      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 (at) gmail.com).
     28  */
     29 
     30 /* \summary: REdis Serialization Protocol (RESP) printer */
     31 
     32 #include <config.h>
     33 
     34 #include "netdissect-stdinc.h"
     35 #include "netdissect.h"
     36 #include <limits.h>
     37 
     38 #include "extract.h"
     39 
     40 
     41 /*
     42  * For information regarding RESP, see: https://redis.io/topics/protocol
     43  */
     44 
     45 #define RESP_SIMPLE_STRING    '+'
     46 #define RESP_ERROR            '-'
     47 #define RESP_INTEGER          ':'
     48 #define RESP_BULK_STRING      '$'
     49 #define RESP_ARRAY            '*'
     50 
     51 #define resp_print_empty(ndo)            ND_PRINT(" empty")
     52 #define resp_print_null(ndo)             ND_PRINT(" null")
     53 #define resp_print_length_too_large(ndo) ND_PRINT(" length too large")
     54 #define resp_print_length_negative(ndo)  ND_PRINT(" length negative and not -1")
     55 #define resp_print_invalid(ndo)          ND_PRINT(" invalid")
     56 
     57 static int resp_parse(netdissect_options *, const u_char *, int);
     58 static int resp_print_string_error_integer(netdissect_options *, const u_char *, int);
     59 static int resp_print_simple_string(netdissect_options *, const u_char *, int);
     60 static int resp_print_integer(netdissect_options *, const u_char *, int);
     61 static int resp_print_error(netdissect_options *, const u_char *, int);
     62 static int resp_print_bulk_string(netdissect_options *, const u_char *, int);
     63 static int resp_print_bulk_array(netdissect_options *, const u_char *, int);
     64 static int resp_print_inline(netdissect_options *, const u_char *, int);
     65 static int resp_get_length(netdissect_options *, const u_char *, int, const u_char **);
     66 
     67 #define LCHECK2(_tot_len, _len) \
     68     {                           \
     69         if (_tot_len < _len)    \
     70             goto trunc;         \
     71     }
     72 
     73 #define LCHECK(_tot_len) LCHECK2(_tot_len, 1)
     74 
     75 /*
     76  * FIND_CRLF:
     77  * Attempts to move our 'ptr' forward until a \r\n is found,
     78  * while also making sure we don't exceed the buffer '_len'
     79  * or go past the end of the captured data.
     80  * If we exceed or go past the end of the captured data,
     81  * jump to trunc.
     82  */
     83 #define FIND_CRLF(_ptr, _len)                   \
     84     for (;;) {                                  \
     85         LCHECK2(_len, 2);                       \
     86         ND_TCHECK_2(_ptr);                      \
     87         if (GET_U_1(_ptr) == '\r' &&            \
     88             GET_U_1(_ptr+1) == '\n')            \
     89             break;                              \
     90         _ptr++;                                 \
     91         _len--;                                 \
     92     }
     93 
     94 /*
     95  * CONSUME_CRLF
     96  * Consume a CRLF that we've just found.
     97  */
     98 #define CONSUME_CRLF(_ptr, _len) \
     99     _ptr += 2;                   \
    100     _len -= 2;
    101 
    102 /*
    103  * FIND_CR_OR_LF
    104  * Attempts to move our '_ptr' forward until a \r or \n is found,
    105  * while also making sure we don't exceed the buffer '_len'
    106  * or go past the end of the captured data.
    107  * If we exceed or go past the end of the captured data,
    108  * jump to trunc.
    109  */
    110 #define FIND_CR_OR_LF(_ptr, _len)           \
    111     for (;;) {                              \
    112         LCHECK(_len);                       \
    113         if (GET_U_1(_ptr) == '\r' ||        \
    114             GET_U_1(_ptr) == '\n')          \
    115             break;                          \
    116         _ptr++;                             \
    117         _len--;                             \
    118     }
    119 
    120 /*
    121  * CONSUME_CR_OR_LF
    122  * Consume all consecutive \r and \n bytes.
    123  * If we exceed '_len' or go past the end of the captured data,
    124  * jump to trunc.
    125  */
    126 #define CONSUME_CR_OR_LF(_ptr, _len)             \
    127     {                                            \
    128         int _found_cr_or_lf = 0;                 \
    129         for (;;) {                               \
    130             /*                                   \
    131              * Have we hit the end of data?      \
    132              */                                  \
    133             if (_len == 0 || !ND_TTEST_1(_ptr)) {\
    134                 /*                               \
    135                  * Yes.  Have we seen a \r       \
    136                  * or \n?                        \
    137                  */                              \
    138                 if (_found_cr_or_lf) {           \
    139                     /*                           \
    140                      * Yes.  Just stop.          \
    141                      */                          \
    142                     break;                       \
    143                 }                                \
    144                 /*                               \
    145                  * No.  We ran out of packet.    \
    146                  */                              \
    147                 goto trunc;                      \
    148             }                                    \
    149             if (GET_U_1(_ptr) != '\r' &&         \
    150                 GET_U_1(_ptr) != '\n')           \
    151                 break;                           \
    152             _found_cr_or_lf = 1;                 \
    153             _ptr++;                              \
    154             _len--;                              \
    155         }                                        \
    156     }
    157 
    158 /*
    159  * SKIP_OPCODE
    160  * Skip over the opcode character.
    161  * The opcode has already been fetched, so we know it's there, and don't
    162  * need to do any checks.
    163  */
    164 #define SKIP_OPCODE(_ptr, _tot_len) \
    165     _ptr++;                         \
    166     _tot_len--;
    167 
    168 /*
    169  * GET_LENGTH
    170  * Get a bulk string or array length.
    171  */
    172 #define GET_LENGTH(_ndo, _tot_len, _ptr, _len)                \
    173     {                                                         \
    174         const u_char *_endp;                                  \
    175         _len = resp_get_length(_ndo, _ptr, _tot_len, &_endp); \
    176         _tot_len -= (_endp - _ptr);                           \
    177         _ptr = _endp;                                         \
    178     }
    179 
    180 /*
    181  * TEST_RET_LEN
    182  * If ret_len is < 0, jump to the trunc tag which returns (-1)
    183  * and 'bubbles up' to printing tstr. Otherwise, return ret_len.
    184  */
    185 #define TEST_RET_LEN(rl) \
    186     if (rl < 0) { goto trunc; } else { return rl; }
    187 
    188 /*
    189  * TEST_RET_LEN_NORETURN
    190  * If ret_len is < 0, jump to the trunc tag which returns (-1)
    191  * and 'bubbles up' to printing tstr. Otherwise, continue onward.
    192  */
    193 #define TEST_RET_LEN_NORETURN(rl) \
    194     if (rl < 0) { goto trunc; }
    195 
    196 /*
    197  * RESP_PRINT_SEGMENT
    198  * Prints a segment in the form of: ' "<stuff>"\n"
    199  * Assumes the data has already been verified as present.
    200  */
    201 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len)            \
    202     ND_PRINT(" \"");                                   \
    203     if (nd_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \
    204         goto trunc;                                    \
    205     fn_print_char(_ndo, '"');
    206 
    207 void
    208 resp_print(netdissect_options *ndo, const u_char *bp, u_int length)
    209 {
    210     int ret_len = 0;
    211 
    212     ndo->ndo_protocol = "resp";
    213 
    214     ND_PRINT(": RESP");
    215     while (length > 0) {
    216         /*
    217          * This block supports redis pipelining.
    218          * For example, multiple operations can be pipelined within the same string:
    219          * "*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"
    220          * or
    221          * "PING\r\nPING\r\nPING\r\n"
    222          * In order to handle this case, we must try and parse 'bp' until
    223          * 'length' bytes have been processed or we reach a trunc condition.
    224          */
    225         ret_len = resp_parse(ndo, bp, length);
    226         TEST_RET_LEN_NORETURN(ret_len);
    227         bp += ret_len;
    228         length -= ret_len;
    229     }
    230 
    231     return;
    232 
    233 trunc:
    234     nd_print_trunc(ndo);
    235 }
    236 
    237 static int
    238 resp_parse(netdissect_options *ndo, const u_char *bp, int length)
    239 {
    240     u_char op;
    241     int ret_len;
    242 
    243     LCHECK2(length, 1);
    244     op = GET_U_1(bp);
    245 
    246     /* bp now points to the op, so these routines must skip it */
    247     switch(op) {
    248         case RESP_SIMPLE_STRING:  ret_len = resp_print_simple_string(ndo, bp, length);   break;
    249         case RESP_INTEGER:        ret_len = resp_print_integer(ndo, bp, length);         break;
    250         case RESP_ERROR:          ret_len = resp_print_error(ndo, bp, length);           break;
    251         case RESP_BULK_STRING:    ret_len = resp_print_bulk_string(ndo, bp, length);     break;
    252         case RESP_ARRAY:          ret_len = resp_print_bulk_array(ndo, bp, length);      break;
    253         default:                  ret_len = resp_print_inline(ndo, bp, length);          break;
    254     }
    255 
    256     /*
    257      * This gives up with a "truncated" indicator for all errors,
    258      * including invalid packet errors; that's what we want, as
    259      * we have to give up on further parsing in that case.
    260      */
    261     TEST_RET_LEN(ret_len);
    262 
    263 trunc:
    264     return (-1);
    265 }
    266 
    267 static int
    268 resp_print_simple_string(netdissect_options *ndo, const u_char *bp, int length) {
    269     return resp_print_string_error_integer(ndo, bp, length);
    270 }
    271 
    272 static int
    273 resp_print_integer(netdissect_options *ndo, const u_char *bp, int length) {
    274     return resp_print_string_error_integer(ndo, bp, length);
    275 }
    276 
    277 static int
    278 resp_print_error(netdissect_options *ndo, const u_char *bp, int length) {
    279     return resp_print_string_error_integer(ndo, bp, length);
    280 }
    281 
    282 static int
    283 resp_print_string_error_integer(netdissect_options *ndo, const u_char *bp, int length) {
    284     int length_cur = length, len, ret_len;
    285     const u_char *bp_ptr;
    286 
    287     /* bp points to the op; skip it */
    288     SKIP_OPCODE(bp, length_cur);
    289     bp_ptr = bp;
    290 
    291     /*
    292      * bp now prints past the (+-;) opcode, so it's pointing to the first
    293      * character of the string (which could be numeric).
    294      * +OK\r\n
    295      * -ERR ...\r\n
    296      * :02912309\r\n
    297      *
    298      * Find the \r\n with FIND_CRLF().
    299      */
    300     FIND_CRLF(bp_ptr, length_cur);
    301 
    302     /*
    303      * bp_ptr points to the \r\n, so bp_ptr - bp is the length of text
    304      * preceding the \r\n.  That includes the opcode, so don't print
    305      * that.
    306      */
    307     len = ND_BYTES_BETWEEN(bp, bp_ptr);
    308     RESP_PRINT_SEGMENT(ndo, bp, len);
    309     ret_len = 1 /*<opcode>*/ + len /*<string>*/ + 2 /*<CRLF>*/;
    310 
    311     TEST_RET_LEN(ret_len);
    312 
    313 trunc:
    314     return (-1);
    315 }
    316 
    317 static int
    318 resp_print_bulk_string(netdissect_options *ndo, const u_char *bp, int length) {
    319     int length_cur = length, string_len;
    320 
    321     /* bp points to the op; skip it */
    322     SKIP_OPCODE(bp, length_cur);
    323 
    324     /* <length>\r\n */
    325     GET_LENGTH(ndo, length_cur, bp, string_len);
    326 
    327     if (string_len >= 0) {
    328         /* Byte string of length string_len, starting at bp */
    329         if (string_len == 0)
    330             resp_print_empty(ndo);
    331         else {
    332             LCHECK2(length_cur, string_len);
    333             ND_TCHECK_LEN(bp, string_len);
    334             RESP_PRINT_SEGMENT(ndo, bp, string_len);
    335             bp += string_len;
    336             length_cur -= string_len;
    337         }
    338 
    339         /*
    340          * Find the \r\n at the end of the string and skip past it.
    341          * XXX - report an error if the \r\n isn't immediately after
    342          * the item?
    343          */
    344         FIND_CRLF(bp, length_cur);
    345         CONSUME_CRLF(bp, length_cur);
    346     } else {
    347         /* null, truncated, or invalid for some reason */
    348         switch(string_len) {
    349             case (-1):  resp_print_null(ndo);             break;
    350             case (-2):  goto trunc;
    351             case (-3):  resp_print_length_too_large(ndo); break;
    352             case (-4):  resp_print_length_negative(ndo);  break;
    353             default:    resp_print_invalid(ndo);          break;
    354         }
    355     }
    356 
    357     return (length - length_cur);
    358 
    359 trunc:
    360     return (-1);
    361 }
    362 
    363 static int
    364 resp_print_bulk_array(netdissect_options *ndo, const u_char *bp, int length) {
    365     u_int length_cur = length;
    366     int array_len, i, ret_len;
    367 
    368     /* bp points to the op; skip it */
    369     SKIP_OPCODE(bp, length_cur);
    370 
    371     /* <array_length>\r\n */
    372     GET_LENGTH(ndo, length_cur, bp, array_len);
    373 
    374     if (array_len > 0) {
    375         /* non empty array */
    376         for (i = 0; i < array_len; i++) {
    377             ret_len = resp_parse(ndo, bp, length_cur);
    378 
    379             TEST_RET_LEN_NORETURN(ret_len);
    380 
    381             bp += ret_len;
    382             length_cur -= ret_len;
    383         }
    384     } else {
    385         /* empty, null, truncated, or invalid */
    386         switch(array_len) {
    387             case 0:     resp_print_empty(ndo);            break;
    388             case (-1):  resp_print_null(ndo);             break;
    389             case (-2):  goto trunc;
    390             case (-3):  resp_print_length_too_large(ndo); break;
    391             case (-4):  resp_print_length_negative(ndo);  break;
    392             default:    resp_print_invalid(ndo);          break;
    393         }
    394     }
    395 
    396     return (length - length_cur);
    397 
    398 trunc:
    399     return (-1);
    400 }
    401 
    402 static int
    403 resp_print_inline(netdissect_options *ndo, const u_char *bp, int length) {
    404     int length_cur = length;
    405     int len;
    406     const u_char *bp_ptr;
    407 
    408     /*
    409      * Inline commands are simply 'strings' followed by \r or \n or both.
    410      * Redis will do its best to split/parse these strings.
    411      * This feature of redis is implemented to support the ability of
    412      * command parsing from telnet/nc sessions etc.
    413      *
    414      * <string><\r||\n||\r\n...>
    415      */
    416 
    417     /*
    418      * Skip forward past any leading \r, \n, or \r\n.
    419      */
    420     CONSUME_CR_OR_LF(bp, length_cur);
    421     bp_ptr = bp;
    422 
    423     /*
    424      * Scan forward looking for \r or \n.
    425      */
    426     FIND_CR_OR_LF(bp_ptr, length_cur);
    427 
    428     /*
    429      * Found it; bp_ptr points to the \r or \n, so bp_ptr - bp is the
    430      * Length of the line text that precedes it.  Print it.
    431      */
    432     len = ND_BYTES_BETWEEN(bp, bp_ptr);
    433     RESP_PRINT_SEGMENT(ndo, bp, len);
    434 
    435     /*
    436      * Skip forward past the \r, \n, or \r\n.
    437      */
    438     CONSUME_CR_OR_LF(bp_ptr, length_cur);
    439 
    440     /*
    441      * Return the number of bytes we processed.
    442      */
    443     return (length - length_cur);
    444 
    445 trunc:
    446     return (-1);
    447 }
    448 
    449 static int
    450 resp_get_length(netdissect_options *ndo, const u_char *bp, int len, const u_char **endp)
    451 {
    452     int result;
    453     u_char c;
    454     int saw_digit;
    455     int neg;
    456     int too_large;
    457 
    458     if (len == 0)
    459         goto trunc;
    460     too_large = 0;
    461     neg = 0;
    462     if (GET_U_1(bp) == '-') {
    463         neg = 1;
    464         bp++;
    465         len--;
    466     }
    467     result = 0;
    468     saw_digit = 0;
    469 
    470     for (;;) {
    471         if (len == 0)
    472             goto trunc;
    473         c = GET_U_1(bp);
    474         if (!(c >= '0' && c <= '9')) {
    475             if (!saw_digit) {
    476                 bp++;
    477                 goto invalid;
    478             }
    479             break;
    480         }
    481         c -= '0';
    482         if (result > (INT_MAX / 10)) {
    483             /* This will overflow an int when we multiply it by 10. */
    484             too_large = 1;
    485         } else {
    486             result *= 10;
    487             if (result == ((INT_MAX / 10) * 10) && c > (INT_MAX % 10)) {
    488                 /* This will overflow an int when we add c */
    489                 too_large = 1;
    490             } else
    491                 result += c;
    492         }
    493         bp++;
    494         len--;
    495         saw_digit = 1;
    496     }
    497 
    498     /*
    499      * OK, we found a non-digit character.  It should be a \r, followed
    500      * by a \n.
    501      */
    502     if (GET_U_1(bp) != '\r') {
    503         bp++;
    504         goto invalid;
    505     }
    506     bp++;
    507     len--;
    508     if (len == 0)
    509         goto trunc;
    510     if (GET_U_1(bp) != '\n') {
    511         bp++;
    512         goto invalid;
    513     }
    514     bp++;
    515     len--;
    516     *endp = bp;
    517     if (neg) {
    518         /* -1 means "null", anything else is invalid */
    519         if (too_large || result != 1)
    520             return (-4);
    521         result = -1;
    522     }
    523     return (too_large ? -3 : result);
    524 
    525 trunc:
    526     *endp = bp;
    527     return (-2);
    528 
    529 invalid:
    530     *endp = bp;
    531     return (-5);
    532 }
    533