mirror of
https://github.com/the-tcpdump-group/tcpdump.git
synced 2024-12-05 07:54:35 +08:00
b38f324af9
Moreover: Remove some redundant comments Update some summary comments Update the specification URL for ATA over Ethernet (AoE) protocol
375 lines
11 KiB
C
375 lines
11 KiB
C
/*
|
|
* 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 (andrew.darqui@gmail.com).
|
|
*/
|
|
|
|
/* \summary: REdis Serialization Protocol (RESP) printer */
|
|
|
|
#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);
|
|
}
|