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