/* packet-memcache.c * Routines for Memcache Binary Protocol * http://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol * * Copyright 2009, Stig Bjorlykke * * Routines for Memcache Textual Protocol * http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt * * Copyright 2009, Rama Chitta * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include /* for sscanf() */ #include /* for strtoul() */ #include #include #include #include #include "packet-tcp.h" void proto_register_memcache (void); void proto_reg_handoff_memcache(void); #define PNAME "Memcache Protocol" #define PSNAME "MEMCACHE" #define PFNAME "memcache" #define MEMCACHE_DEFAULT_RANGE "11211" #define MEMCACHE_HEADER_LEN 24 /* Magic Byte */ #define MAGIC_REQUEST 0x80 #define MAGIC_RESPONSE 0x81 /* Response Status */ #define RS_NO_ERROR 0x0000 #define RS_KEY_NOT_FOUND 0x0001 #define RS_KEY_EXISTS 0x0002 #define RS_VALUE_TOO_BIG 0x0003 #define RS_INVALID_ARGUMENTS 0x0004 #define RS_ITEM_NOT_STORED 0x0005 #define RS_UNKNOWN_COMMAND 0x0081 #define RS_OUT_OF_MEMORY 0x0082 /* Command Opcodes */ #define OP_GET 0x00 #define OP_SET 0x01 #define OP_ADD 0x02 #define OP_REPLACE 0x03 #define OP_DELETE 0x04 #define OP_INCREMENT 0x05 #define OP_DECREMENT 0x06 #define OP_QUIT 0x07 #define OP_FLUSH 0x08 #define OP_GET_Q 0x09 #define OP_NO_OP 0x0A #define OP_VERSION 0x0B #define OP_GET_K 0x0C #define OP_GET_K_Q 0x0D #define OP_APPEND 0x0E #define OP_PREPEND 0x0F #define OP_STAT 0x10 #define OP_SET_Q 0x11 #define OP_ADD_Q 0x12 #define OP_REPLACE_Q 0x13 #define OP_DELETE_Q 0x14 #define OP_INCREMENT_Q 0x15 #define OP_DECREMENT_Q 0x16 #define OP_QUIT_Q 0x17 #define OP_FLUSH_Q 0x18 #define OP_APPEND_Q 0x19 #define OP_PREPEND_Q 0x1A /* Internally defined command opcodes used in the textual dissector only */ /* This values are not defined in any standard and can be redefined here */ #define OP_GETS 0xF0 #define OP_CAS 0xF1 #define OP_VERBOSE 0xF2 /* Data Types */ #define DT_RAW_BYTES 0x00 static int proto_memcache = -1; static range_t *memcache_tcp_port_range = NULL; static range_t *memcache_udp_port_range = NULL; static dissector_handle_t memcache_tcp_handle; static dissector_handle_t memcache_udp_handle; static int hf_magic = -1; static int hf_opcode = -1; static int hf_extras_length = -1; static int hf_key_length = -1; static int hf_value_length = -1; static int hf_data_type = -1; static int hf_reserved = -1; static int hf_status = -1; static int hf_total_body_length = -1; static int hf_opaque = -1; static int hf_cas = -1; static int hf_extras = -1; static int hf_extras_flags = -1; static int hf_extras_expiration = -1; static int hf_extras_delta = -1; static int hf_extras_initial = -1; static int hf_extras_unknown = -1; static int hf_key = -1; static int hf_value = -1; static int hf_uint64_response = -1; static int hf_command = -1; static int hf_subcommand = -1; static int hf_flags = -1; static int hf_expiration = -1; static int hf_noreply = -1; static int hf_response = -1; static int hf_version = -1; static int hf_slabclass = -1; static int hf_name = -1; static int hf_name_value = -1; static gint ett_memcache = -1; static gint ett_extras = -1; static expert_field ei_value_missing = EI_INIT; static expert_field ei_extras_missing = EI_INIT; static expert_field ei_value_length = EI_INIT; static expert_field ei_key_missing = EI_INIT; static expert_field ei_key_unknown = EI_INIT; static expert_field ei_extras_unknown = EI_INIT; static expert_field ei_value_unknown = EI_INIT; static expert_field ei_status_response = EI_INIT; static expert_field ei_opcode_unknown = EI_INIT; static expert_field ei_reserved_value = EI_INIT; static expert_field ei_magic_unknown = EI_INIT; static const value_string magic_vals[] = { { MAGIC_REQUEST, "Request" }, { MAGIC_RESPONSE, "Response" }, { 0, NULL } }; static const value_string status_vals[] = { { RS_NO_ERROR, "No error" }, { RS_KEY_NOT_FOUND, "Key not found" }, { RS_KEY_EXISTS, "Key exists" }, { RS_VALUE_TOO_BIG, "Value too big" }, { RS_INVALID_ARGUMENTS, "Invalid arguments" }, { RS_ITEM_NOT_STORED, "Item not stored" }, { RS_UNKNOWN_COMMAND, "Unknown command" }, { RS_OUT_OF_MEMORY, "Out of memory" }, { 0, NULL } }; static const value_string opcode_vals[] = { { OP_GET, "Get" }, { OP_SET, "Set" }, { OP_ADD, "Add" }, { OP_REPLACE, "Replace" }, { OP_DELETE, "Delete" }, { OP_INCREMENT, "Increment" }, { OP_DECREMENT, "Decrement" }, { OP_QUIT, "Quit" }, { OP_FLUSH, "Flush" }, { OP_GET_Q, "Get Quietly" }, { OP_NO_OP, "No-op" }, { OP_VERSION, "Version" }, { OP_GET_K, "Get Key" }, { OP_GET_K_Q, "Get Key Quietly" }, { OP_APPEND, "Append" }, { OP_PREPEND, "Prepend" }, { OP_STAT, "Statistics" }, { OP_SET_Q, "Set Quietly" }, { OP_ADD_Q, "Add Quietly" }, { OP_REPLACE_Q, "Replace Quietly" }, { OP_DELETE_Q, "Delete Quietly" }, { OP_INCREMENT_Q, "Increment Quietly" }, { OP_DECREMENT_Q, "Decrement Quietly" }, { OP_QUIT_Q, "Quit Quietly" }, { OP_FLUSH_Q, "Flush Quietly" }, { OP_APPEND_Q, "Append Quietly" }, { OP_PREPEND_Q, "Prepend Quietly" }, /* Internally defined values not valid here */ { 0, NULL } }; static const value_string data_type_vals[] = { { DT_RAW_BYTES, "Raw bytes" }, { 0, NULL } }; /* memcache message types. */ typedef enum _memcache_type { MEMCACHE_REQUEST, MEMCACHE_RESPONSE, MEMCACHE_UNKNOWN } memcache_type_t; /* desegmentation of MEMCACHE header */ static gboolean memcache_desegment_headers = TRUE; /* desegmentation of MEMCACHE payload */ static gboolean memcache_desegment_body = TRUE; /* should refer to either the request or the response dissector. */ typedef int (*ReqRespDissector)(tvbuff_t*, packet_info *, proto_tree *, int, const guchar*, const guchar*, guint8); /* determines if a packet contains a memcache * request or reply by looking at its first token. */ static int is_memcache_request_or_reply(const gchar *data, int linelen, guint8 *opcode, memcache_type_t *type, int *expect_content_length, ReqRespDissector *reqresp_dissector); static guint get_memcache_pdu_len (packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) { guint32 body_len; /* Get the length of the memcache body */ body_len = tvb_get_ntohl(tvb, offset+8); /* That length doesn't include the header; add that in */ return body_len + MEMCACHE_HEADER_LEN; } static void dissect_extras (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, guint8 extras_len, guint8 opcode, gboolean request) { proto_tree *extras_tree = NULL; proto_item *extras_item = NULL, *ti; gint save_offset = offset; gboolean illegal = FALSE; /* Set when extras shall not be present */ gboolean missing = FALSE; /* Set when extras is missing */ if (extras_len) { extras_item = proto_tree_add_item (tree, hf_extras, tvb, offset, extras_len, ENC_NA); extras_tree = proto_item_add_subtree (extras_item, ett_extras); } switch (opcode) { case OP_GET: case OP_GET_Q: case OP_GET_K: case OP_GET_K_Q: if (extras_len) { if (request) { /* Request shall not have extras */ illegal = TRUE; } else { proto_tree_add_item (extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; } } else if (!request) { /* Response must have extras */ missing = TRUE; } break; case OP_SET: case OP_SET_Q: case OP_ADD: case OP_ADD_Q: case OP_REPLACE: case OP_REPLACE_Q: if (extras_len) { if (request) { proto_tree_add_item (extras_tree, hf_extras_flags, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item (extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; } else { /* Response shall not have extras */ illegal = TRUE; } } else if (request) { /* Request must have extras */ missing = TRUE; } break; case OP_INCREMENT: case OP_INCREMENT_Q: case OP_DECREMENT: case OP_DECREMENT_Q: if (extras_len) { if (request) { proto_tree_add_item (extras_tree, hf_extras_delta, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item (extras_tree, hf_extras_initial, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item (extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; } else { /* Response must not have extras (response is in Value) */ illegal = TRUE; } } else if (request) { /* Request must have extras */ missing = TRUE; } break; case OP_FLUSH: case OP_FLUSH_Q: if (extras_len) { proto_tree_add_item (extras_tree, hf_extras_expiration, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; } break; case OP_DELETE: case OP_DELETE_Q: case OP_QUIT: case OP_QUIT_Q: case OP_VERSION: case OP_APPEND: case OP_APPEND_Q: case OP_PREPEND: case OP_PREPEND_Q: case OP_STAT: /* Must not have extras */ if (extras_len) { illegal = TRUE; } break; default: if (extras_len) { /* Decode as unknown extras */ proto_tree_add_item (extras_tree, hf_extras_unknown, tvb, offset, extras_len, ENC_NA); offset += extras_len; } break; } if (illegal) { ti = proto_tree_add_item (extras_tree, hf_extras_unknown, tvb, offset, extras_len, ENC_NA); expert_add_info_format(pinfo, ti, &ei_extras_unknown, "%s %s shall not have Extras", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); offset += extras_len; } else if (missing) { proto_tree_add_expert_format(tree, pinfo, &ei_extras_missing, tvb, offset, 0, "%s %s must have Extras", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } if ((offset - save_offset) != extras_len) { expert_add_info_format(pinfo, extras_item, &ei_extras_unknown, "Illegal Extras length, should be %d", offset - save_offset); } } static void dissect_key (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, int key_len, guint8 opcode, gboolean request) { proto_item *ti = NULL; gboolean illegal = FALSE; /* Set when key shall not be present */ gboolean missing = FALSE; /* Set when key is missing */ if (key_len) { ti = proto_tree_add_item (tree, hf_key, tvb, offset, key_len, ENC_ASCII|ENC_NA); offset += key_len; if ((opcode == OP_QUIT) || (opcode == OP_QUIT_Q) || (opcode == OP_NO_OP) || (opcode == OP_VERSION)) { /* Request and Response must not have key */ illegal = TRUE; } if ((opcode == OP_SET) || (opcode == OP_ADD) || (opcode == OP_REPLACE) || (opcode == OP_DELETE) || (opcode == OP_SET_Q) || (opcode == OP_ADD_Q) || (opcode == OP_REPLACE_Q) || (opcode == OP_DELETE_Q) || (opcode == OP_FLUSH) || (opcode == OP_APPEND) || (opcode == OP_PREPEND) || (opcode == OP_FLUSH_Q) || (opcode == OP_APPEND_Q) || (opcode == OP_PREPEND_Q)) { /* Response must not have a key */ if (!request) { illegal = TRUE; } } } else { if ((opcode == OP_GET) || (opcode == OP_GET_Q) || (opcode == OP_GET_K) || (opcode == OP_GET_K_Q) || (opcode == OP_SET) || (opcode == OP_ADD) || (opcode == OP_REPLACE) || (opcode == OP_DELETE) || (opcode == OP_SET_Q) || (opcode == OP_ADD_Q) || (opcode == OP_REPLACE_Q) || (opcode == OP_DELETE_Q) || (opcode == OP_INCREMENT) || (opcode == OP_DECREMENT) || (opcode == OP_INCREMENT_Q) || (opcode == OP_DECREMENT_Q)) { /* Request must have key */ if (request) { missing = TRUE; } } } if (illegal) { expert_add_info_format(pinfo, ti, &ei_key_unknown, "%s %s shall not have Key", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } else if (missing) { proto_tree_add_expert_format(tree, pinfo, &ei_key_missing, tvb, offset, 0, "%s Request must have Key", val_to_str (opcode, opcode_vals, "Opcode %d")); } } static void dissect_value (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, guint32 value_len, guint8 opcode, gboolean request) { proto_item *ti = NULL; gboolean illegal = FALSE; /* Set when value shall not be present */ gboolean missing = FALSE; /* Set when value is missing */ if (value_len > 0) { if (!request && ((opcode == OP_INCREMENT) || (opcode == OP_DECREMENT))) { ti = proto_tree_add_item (tree, hf_uint64_response, tvb, offset, 8, ENC_BIG_ENDIAN); if (value_len != 8) { expert_add_info_format(pinfo, ti, &ei_value_length, "Illegal Value length, should be 8"); } } else { ti = proto_tree_add_item (tree, hf_value, tvb, offset, value_len, ENC_ASCII|ENC_NA); } offset += value_len; } /* Sanity check */ if (value_len) { if ((opcode == OP_GET) || (opcode == OP_GET_Q) || (opcode == OP_GET_K) || (opcode == OP_GET_K_Q) || (opcode == OP_INCREMENT) || (opcode == OP_DECREMENT) || (opcode == OP_VERSION) || (opcode == OP_INCREMENT_Q) || (opcode == OP_DECREMENT_Q)) { /* Request must not have value */ if (request) { illegal = TRUE; } } if ((opcode == OP_DELETE) || (opcode == OP_QUIT) || (opcode == OP_FLUSH) || (opcode == OP_NO_OP) || (opcode == OP_DELETE_Q) || (opcode == OP_QUIT_Q) || (opcode == OP_FLUSH_Q)) { /* Request and Response must not have value */ illegal = TRUE; } if ((opcode == OP_SET) || (opcode == OP_ADD) || (opcode == OP_REPLACE) || (opcode == OP_SET_Q) || (opcode == OP_ADD_Q) || (opcode == OP_REPLACE_Q) || (opcode == OP_APPEND) || (opcode == OP_PREPEND) || (opcode == OP_APPEND_Q) || (opcode == OP_PREPEND_Q)) { /* Response must not have value */ if (!request) { illegal = TRUE; } } } else { if ((opcode == OP_SET) || (opcode == OP_ADD) || (opcode == OP_REPLACE) || (opcode == OP_SET_Q) || (opcode == OP_ADD_Q) || (opcode == OP_REPLACE_Q) || (opcode == OP_APPEND) || (opcode == OP_PREPEND) || (opcode == OP_APPEND_Q) || (opcode == OP_PREPEND_Q)) { /* Request must have a value */ if (request) { missing = TRUE; } } } if (illegal) { expert_add_info_format(pinfo, ti, &ei_value_unknown, "%s %s shall not have Value", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } else if (missing) { proto_tree_add_expert_format(tree, pinfo, &ei_value_missing, tvb, offset, 0, "%s %s must have Value", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } } static int dissect_memcache (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) { proto_tree *memcache_tree; proto_item *memcache_item, *ti; gint offset = 0; guint8 magic, opcode, extras_len; guint16 key_len, status = 0; guint32 body_len, value_len; gboolean request; col_set_str (pinfo->cinfo, COL_PROTOCOL, PSNAME); col_clear (pinfo->cinfo, COL_INFO); memcache_item = proto_tree_add_item (tree, proto_memcache, tvb, offset, -1, ENC_NA); memcache_tree = proto_item_add_subtree (memcache_item, ett_memcache); magic = tvb_get_guint8 (tvb, offset); ti = proto_tree_add_item (memcache_tree, hf_magic, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; if (try_val_to_str (magic, magic_vals) == NULL) { expert_add_info_format(pinfo, ti, &ei_magic_unknown, "Unknown magic byte: %d", magic); } opcode = tvb_get_guint8 (tvb, offset); ti = proto_tree_add_item (memcache_tree, hf_opcode, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; if (try_val_to_str (opcode, opcode_vals) == NULL) { expert_add_info_format(pinfo, ti, &ei_opcode_unknown, "Unknown opcode: %d", opcode); } proto_item_append_text (memcache_item, ", %s %s", val_to_str (opcode, opcode_vals, "Unknown opcode (%d)"), val_to_str (magic, magic_vals, "Unknown magic (%d)")); col_append_fstr (pinfo->cinfo, COL_INFO, "%s %s", val_to_str (opcode, opcode_vals, "Unknown opcode (%d)"), val_to_str (magic, magic_vals, "Unknown magic (%d)")); key_len = tvb_get_ntohs (tvb, offset); proto_tree_add_item (memcache_tree, hf_key_length, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; extras_len = tvb_get_guint8 (tvb, offset); proto_tree_add_item (memcache_tree, hf_extras_length, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item (memcache_tree, hf_data_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; status = tvb_get_ntohs (tvb, offset); if (magic & 0x01) { /* We suppose this is a response, even when unknown magic byte */ request = FALSE; ti = proto_tree_add_item (memcache_tree, hf_status, tvb, offset, 2, ENC_BIG_ENDIAN); if (status != 0) { expert_add_info_format(pinfo, ti, &ei_status_response, "%s: %s", val_to_str (opcode, opcode_vals, "Unknown opcode (%d)"), val_to_str (status, status_vals, "Status: %d")); } } else { request = TRUE; ti = proto_tree_add_item (memcache_tree, hf_reserved, tvb, offset, 2, ENC_BIG_ENDIAN); if (status != 0) { expert_add_info_format(pinfo, ti, &ei_reserved_value, "Reserved value: %d", status); } } offset += 2; body_len = tvb_get_ntohl (tvb, offset); value_len = body_len - extras_len - key_len; ti = proto_tree_add_uint (memcache_tree, hf_value_length, tvb, offset, 0, value_len); PROTO_ITEM_SET_GENERATED (ti); proto_tree_add_item (memcache_tree, hf_total_body_length, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item (memcache_tree, hf_opaque, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item (memcache_tree, hf_cas, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; if (status == 0) { dissect_extras (tvb, pinfo, memcache_tree, offset, extras_len, opcode, request); offset += extras_len; dissect_key (tvb, pinfo, memcache_tree, offset, key_len, opcode, request); offset += key_len; dissect_value (tvb, pinfo, memcache_tree, offset, value_len, opcode, request); /*offset += value_len;*/ } else if (body_len) { proto_tree_add_item (memcache_tree, hf_value, tvb, offset, body_len, ENC_ASCII|ENC_NA); /*offset += body_len;*/ col_append_fstr (pinfo->cinfo, COL_INFO, " (%s)", val_to_str (status, status_vals, "Unknown status: %d")); } else { proto_tree_add_expert_format(memcache_tree, pinfo, &ei_value_missing, tvb, offset, 0, "%s with status %s (%d) must have Value", val_to_str (opcode, opcode_vals, "Opcode %d"), val_to_str_const (status, status_vals, "Unknown"), status); } return tvb_captured_length(tvb); } /* Obtain the content length by peeping into the header. */ static gboolean get_payload_length (tvbuff_t *tvb, const int token_number, int offset, guint32 *bytes, gboolean *content_length_found) { const guchar *next_token; const guchar *line, *lineend; guchar *bytes_val; int tokenlen, i = 0, linelen; gint next_offset; /* get the header line. */ linelen = tvb_find_line_end (tvb, offset, -1, &next_offset, FALSE); if (linelen < 0) { return FALSE; } line = tvb_get_ptr (tvb, offset, linelen); lineend = line + linelen; while (++i < token_number) { tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return FALSE; } offset += (int) (next_token - line); line = next_token; } /* line or the next_token has the value we want. */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return FALSE; } bytes_val = tvb_get_string_enc(wmem_packet_scope(), tvb, offset, tokenlen, ENC_ASCII); if (bytes_val) { if (sscanf (bytes_val, "%u", bytes) == 1) { *content_length_found = TRUE; } else { return FALSE; } } else { return FALSE; } /* reached this far, we got what we want. */ return TRUE; } /* check if a PDU needs to be desegmented. */ static gboolean desegment_pdus (tvbuff_t *tvb, packet_info *pinfo, const int offset, const int data_offset, guint32 content_length) { gint length_remaining, reported_length_remaining; /* data_offset has been set to start of the data block. */ if (!tvb_bytes_exist (tvb, data_offset, content_length)) { length_remaining = tvb_captured_length_remaining (tvb, data_offset); reported_length_remaining = tvb_reported_length_remaining (tvb, data_offset); if (length_remaining < reported_length_remaining) { /* It's a waste of time asking for more * data, because that data wasn't captured. */ return FALSE; } if (length_remaining == -1) { length_remaining = 0; } pinfo->desegment_offset = offset; /* start of the packet. */ pinfo->desegment_len = (content_length + 2) - length_remaining; /* add 2 for /r/n */ return TRUE; } return FALSE; } /* * Optionally do reassembly of the requests, responses and data. */ static gboolean memcache_req_resp_hdrs_do_reassembly ( tvbuff_t *tvb, const int offset, packet_info *pinfo, const gboolean desegment_headers, const gboolean desegment_body, const memcache_type_t type, const int expect_content_length) { int linelen; gint next_offset; gint length_remaining; gint reported_length_remaining; guint32 content_length = 0; gboolean content_length_found = FALSE; gboolean ret = FALSE; /* * If header desegmentation is activated, check the * header in this tvbuff. * request one more byte (we don't know how many bytes * we'll need, so we just ask for one). */ if (desegment_headers && pinfo->can_desegment) { next_offset = offset; reported_length_remaining = tvb_reported_length_remaining (tvb, next_offset); /* * Request one more byte if there're no * bytes left in the reported data (if there're * bytes left in the reported data, but not in * the available data, requesting more bytes * won't help, as those bytes weren't captured). */ if (reported_length_remaining < 1) { pinfo->desegment_offset = offset; pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; return FALSE; } length_remaining = tvb_captured_length_remaining (tvb, next_offset); /* Request one more byte if we cannot find a * header (i.e. a line end). */ linelen = tvb_find_line_end (tvb, next_offset, -1, &next_offset, TRUE); if (linelen == -1 && length_remaining >= reported_length_remaining) { /* Not enough data; ask for one more byte. */ pinfo->desegment_offset = offset; pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; return FALSE; } /* Browse through the header to find the content length. * * request: * [noreply]\r\n * cas [noreply]\r\n * * response: * VALUE []\r\n * \r\n */ if (expect_content_length == TRUE) { switch (type) { case MEMCACHE_REQUEST: /* Get the fifth token in the header.*/ ret = get_payload_length (tvb, 5 , offset, &content_length, &content_length_found); if (!ret) { return FALSE; } break; case MEMCACHE_RESPONSE: /* Get the fourth token in the header.*/ ret = get_payload_length (tvb, 4 , offset, &content_length, &content_length_found); if (!ret) { return FALSE; } break; default: /* Unrecognized message type. */ return FALSE; } } } /* We have reached the end of a header, so there * should be 'content_length' bytes after this * followed by CRLF. The next_offset points to the * start of the data bytes. */ if (desegment_body && content_length_found) { return !desegment_pdus (tvb, pinfo, offset, next_offset, content_length); } /* No further desegmentation needed. */ return TRUE; } /* Dissect a memcache message. */ static int dissect_memcache_message (tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree) { const guchar *line; const guchar *lineend; int orig_offset; int first_linelen; int datalen; int expect_content_length = FALSE; gint next_offset; gboolean is_request_or_reply; memcache_type_t memcache_type; ReqRespDissector reqresp_dissector = NULL; proto_tree *memcache_tree = NULL; proto_item *memcache_item = NULL; guint8 opcode = 0xff; /* set to something that is not in the list. */ /* Find a line end in the packet. * Note that "tvb_find_line_end ()" will return a value that * is not longer than what's in the buffer, so the * "tvb_get_ptr ()" call won't throw an exception. */ first_linelen = tvb_find_line_end (tvb, offset, -1, &next_offset, FALSE); if (first_linelen < 0) { return -1; } line = tvb_get_ptr (tvb, offset, first_linelen); lineend = line + first_linelen; memcache_type = MEMCACHE_UNKNOWN; /* packet type not known yet */ /* Look at the first token of the first line to * determine if it is a request or a response? */ is_request_or_reply = is_memcache_request_or_reply ((const gchar *)line, first_linelen, &opcode, &memcache_type, &expect_content_length, &reqresp_dissector); if (is_request_or_reply) { /* Yes, it is a request or a response. * Do header and body desegmentation if we've been told to. */ if (!memcache_req_resp_hdrs_do_reassembly (tvb, offset, pinfo, memcache_desegment_headers, memcache_desegment_body, memcache_type, expect_content_length)) { /* More data needed for desegmentation. */ return -1; } } /* Columns and summary display. */ col_set_str (pinfo->cinfo, COL_PROTOCOL, PSNAME); /* If the packet is a memcache request or reply, * put the first line from the buffer into the summary * Otherwise, just call it a continuation. */ if (is_request_or_reply) { line = tvb_get_ptr (tvb, offset, first_linelen); col_add_fstr (pinfo->cinfo, COL_INFO, "%s ", format_text (line, first_linelen)); } else { col_set_str (pinfo->cinfo, COL_INFO, "MEMCACHE Continuation"); } orig_offset = offset; memcache_item = proto_tree_add_item (tree, proto_memcache, tvb, offset, -1, ENC_NA); memcache_tree = proto_item_add_subtree (memcache_item, ett_memcache); /* Process the packet data. The first line is expected to be a * header. If it's not a header then we don't dissect. * At this point, we already know if it is a request or a * response. */ if (tvb_reported_length_remaining (tvb, offset) != 0) { /* Dissect a request or a response. */ if (is_request_or_reply && reqresp_dissector) { next_offset = reqresp_dissector (tvb, pinfo, memcache_tree, offset, line, lineend, opcode); if (next_offset == -1) { /* Error in dissecting. */ return -1; } offset = next_offset; } } /* * If a 'bytes' value was supplied, the amount of data to be * processed as MEMCACHE payload is the minimum of the 'bytes' * value and the amount of data remaining in the frame. * */ datalen = tvb_captured_length_remaining (tvb, offset); if (datalen > 0) { /* * We've processed "datalen" bytes worth of data * (which may be no data at all); advance the * offset past whatever data we've processed. */ offset += datalen; } return offset - orig_offset; } /* Payload dissector * \r\n */ static int content_data_dissector (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int content_length, guint8 opcode) { gint datalen; gboolean short_pkt = FALSE; /* * Expecting to read 'content_length' number of bytes from * the buffer. It is not necessary that we have all the * content_length bytes available to read. */ if (tvb_reported_length_remaining (tvb, offset) != 0) { /* bytes actually remaining in this tvbuff. */ datalen = tvb_captured_length_remaining (tvb, offset); if (content_length >= 0) { if (datalen >= (content_length + 2)) { /* also consider \r\n*/ datalen = content_length; } else { short_pkt = TRUE; } } /* dissect the data block. */ dissect_value (tvb, pinfo, tree, offset, datalen, opcode, TRUE); if (datalen > 0) { /* * We've processed "datalen" bytes worth of data * (which may be no data at all); advance the * offset past whatever data we've processed. */ if (!short_pkt) { offset += (datalen + 2); /* go past /r/n*/ } else { offset += datalen; /* short packet; no /r/n*/ } } } return offset; } /* Find the occurrences of a ':' in a stat response. */ static guint find_stat_colon (const guchar *line, const guchar *lineend, const guchar **first_colon, const guchar **last_colon) { const guchar *linep, *temp; guint occurrences = 0; guchar c; linep = line; while (linep < lineend) { temp = linep; c = *linep++; switch (c) { case ':': occurrences++; if (occurrences == 1) { *first_colon = temp; } else if (occurrences == 2) { *last_colon = temp; } else { /* anything other than 1 or 2; * return immediately */ return occurrences; } break; default: break; } } return occurrences; } /* incr/decr response dissector */ static int incr_dissector (tvbuff_t *tvb, proto_tree *tree, int offset) { gint next_offset; int linelen; const guchar *line, *lineend; const guchar *next_token; int tokenlen; /* expecting to read 'bytes' number of bytes from the buffer. */ if (tvb_offset_exists (tvb, offset)) { /* Find the end of the line. */ linelen = tvb_find_line_end (tvb, offset, -1, &next_offset, FALSE); if (linelen < 0) { /* header is out of the packet limits. */ return -1; } /* * Get a buffer that refers to the line. * in other words, the unstructured portion * of memcache. */ line = tvb_get_ptr (tvb, offset, linelen); lineend = line + linelen; /* 64 bit value */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } proto_tree_add_item (tree, hf_uint64_response, tvb, offset, tokenlen, ENC_BIG_ENDIAN); /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return next_offset; } else { return -1; /* invalid token */ } } return offset; } /* stats response dissector */ static int stat_dissector (tvbuff_t *tvb, proto_tree *tree, int offset) { guint occurrences = 0; const guchar *first_colon = NULL, *last_colon = NULL; int tokenlen, linelen; gint next_offset; const guchar *next_token; const guchar *line, *lineend; guint32 slabclass; guchar response_chars[21]; while (tvb_offset_exists (tvb, offset)) { /* Find the end of the line. */ linelen = tvb_find_line_end (tvb, offset, -1, &next_offset, FALSE); if (linelen < 0) { return -1; } /* * Get a buffer that refers to the line. */ line = tvb_get_ptr (tvb, offset, linelen); lineend = line + linelen; tokenlen = get_token_len (line, lineend, &next_token); if ((tokenlen == 4) && strncmp (line, "STAT", tokenlen) == 0) { proto_tree_add_item (tree, hf_command, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; occurrences = find_stat_colon (line, lineend, &first_colon, &last_colon); } else if ((tokenlen == 3) && strncmp (line, "END", tokenlen) == 0) { /* done. reached an end of response. */ offset += (int) (next_token - line); return offset; } else { /* invalid token */ return -1; } switch (occurrences) { case 2: /* stats items: 2 colons */ /* subcommand 'items' */ tokenlen = (int) (first_colon - line); proto_tree_add_item (tree, hf_subcommand, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += tokenlen + 1; /* slabclass */ tokenlen = (int) (last_colon - first_colon - 1); if (tokenlen > 10 || tokenlen <= 0) { return -1; } memcpy (response_chars, first_colon + 1, tokenlen); response_chars[tokenlen] = '\0'; slabclass = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_slabclass, tvb, offset, tokenlen, slabclass); offset += tokenlen + 1; line = last_colon + 1; break; case 1: /* stats slabs: 1 colon */ tokenlen = (int) (first_colon - line); if (tokenlen > 10 || tokenlen <= 0) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; slabclass = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_slabclass, tvb, offset, tokenlen, slabclass); offset += (int) (tokenlen + 1); line = first_colon + 1; break; case 0: /* stats: 0 colons */ break; default: /* invalid token. */ return -1; } /* \r\n */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; /* invalid token */ } proto_tree_add_item (tree, hf_name, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; /* value */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; /* invalid token */ } proto_tree_add_item (tree, hf_name_value, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset = next_offset; } return offset; } /* get/gets response dissector */ static int get_response_dissector (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset) { gint next_offset; int linelen; const guchar *line, *lineend; const guchar *next_token; int tokenlen; guint16 flags; guint32 bytes; guint64 cas; guint8 opcode = 0xff; gchar response_chars[21]; /* cover uint64 (20 + 1) bytes*/ /* expecting to read 'bytes' number of bytes from the buffer. */ while (tvb_offset_exists (tvb, offset)) { /* Find the end of the line. */ linelen = tvb_find_line_end (tvb, offset, -1, &next_offset, FALSE); if (linelen < 0) { /* header is out of the packet limits. */ return -1; } /* * Get a buffer that refers to the line. * in other words, the unstructured portion * of memcache. */ line = tvb_get_ptr (tvb, offset, linelen); lineend = line + linelen; /* VALUE token */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { /* error */ return -1; } if ((tokenlen == 5) && strncmp (line, "VALUE", tokenlen) == 0) { /* proceed */ } else if ((tokenlen == 3) && strncmp (line, "END", tokenlen) == 0) { /* done. reached an end of response. */ offset += (int) (next_token - line); return offset; } else { /* invalid token */ return -1; } offset += (int) (next_token - line); line = next_token; /* key */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } dissect_key (tvb, pinfo, tree, offset, tokenlen, opcode, TRUE); offset += (int) (next_token - line); line = next_token; /* flags */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0 || tokenlen > 5) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; flags = (guint16) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_flags, tvb, offset, tokenlen, flags); offset += (int) (next_token - line); line = next_token; /* bytes */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0 || tokenlen > 10) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; bytes = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_value_length, tvb, offset, tokenlen, bytes); offset += (int) (next_token - line); line = next_token; /* check if cas id is present */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen > 20) { return -1; } if (tokenlen != 0) { /* reached the end of line; CRLF */ memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; cas = (guint64) strtoul (response_chars, NULL, 10); proto_tree_add_uint64 (tree, hf_cas, tvb, offset, tokenlen, cas); /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen != 0) { return -1; /* invalid token */ } } offset = next_offset; /* \r\n */ offset = content_data_dissector (tvb, pinfo, tree, offset, bytes, opcode); if (offset == -1) { return offset; } } return offset; } /* Basic memcache response dissector. */ static int memcache_response_dissector (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, const guchar *line, const guchar *lineend, guint8 opcode) { const guchar *next_token; int tokenlen; switch (opcode) { case OP_GET: case OP_GETS: return get_response_dissector (tvb, pinfo, tree, offset); case OP_VERSION: /* response code. */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } if ((tokenlen == 7) && strncmp (line, "VERSION", tokenlen) == 0) { offset += (int) (next_token - line); line = next_token; } else { return -1; } /* version string */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { /* expecting version string. */ return -1; } proto_tree_add_item (tree, hf_version, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen != 0) { /* invalid token */ return -1; } return offset; case OP_STAT: return stat_dissector (tvb, tree, offset); default: break; } /* response code. */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } /* all the following mark an end of a response. * should take care of set, add, cas, append, replace * prepend, flush_all, verbosity, delete and to an extent * incr, decr and stat commands. */ if ((tokenlen == 6 && strncmp (line, "STORED", tokenlen) == 0) || (tokenlen == 10 && strncmp (line, "NOT_STORED", tokenlen) == 0) || (tokenlen == 6 && strncmp (line, "EXISTS", tokenlen) == 0) || (tokenlen == 9 && strncmp (line, "NOT_FOUND", tokenlen) == 0) || (tokenlen == 7 && strncmp (line, "DELETED", tokenlen) == 0) || (tokenlen == 2 && strncmp (line, "OK", tokenlen) == 0) || (tokenlen == 3 && strncmp (line, "END", tokenlen) == 0)) { proto_tree_add_item (tree, hf_response, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); return offset; } /* if we have reached this point: * it is either an incr/decr response of the format * \r\n. * or * "stats sizes" response of the format: * \r\n */ if (opcode == OP_INCREMENT) { return incr_dissector (tvb, tree, offset); } return offset; } /* Basic memcache request dissector. */ static int memcache_request_dissector (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, const guchar *line, const guchar *lineend, guint8 opcode) { const guchar *next_token; int tokenlen; guint16 flags; guint32 expiration; guint32 bytes; guint64 cas; gchar response_chars[21]; /* cover uint64 (20 + 1) bytes*/ /* command. */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } proto_tree_add_item (tree, hf_command, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; switch (opcode) { case OP_SET: case OP_ADD: case OP_REPLACE: case OP_APPEND: case OP_PREPEND: case OP_CAS: /* key */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } dissect_key (tvb, pinfo, tree, offset, tokenlen, opcode, TRUE); offset += (int) (next_token - line); line = next_token; /* flags */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0 || tokenlen > 5) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; flags = (guint16) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_flags, tvb, offset, tokenlen, flags); offset += (int) (next_token - line); line = next_token; /* expiration */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0 || tokenlen > 10) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; expiration = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_expiration, tvb, offset, tokenlen, expiration); offset += (int) (next_token - line); line = next_token; /* bytes */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0 || tokenlen > 10) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; bytes = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_value_length, tvb, offset, tokenlen, bytes); offset += (int) (next_token - line); line = next_token; /* cas id. */ if (opcode == OP_CAS) { tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0 || tokenlen > 20) { return -1; } memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; cas = (guint64) strtoul (response_chars, NULL, 10); proto_tree_add_uint64 (tree, hf_cas, tvb, offset, tokenlen, cas); offset += (int) (next_token - line); line = next_token; } /* check if the following bit is "noreply" or * the actual data block. */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen != 0) { if (tokenlen == 7 && strncmp (line, "noreply", 7) == 0) { proto_tree_add_item (tree, hf_noreply, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); } offset += (int) (next_token - line); } offset += 2 ; /* go past /r/n*/ /* \r\n */ offset = content_data_dissector (tvb, pinfo, tree, offset, bytes, opcode); if (offset == -1) { return offset; } break; case OP_INCREMENT: case OP_DECREMENT: /* key */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } dissect_key (tvb, pinfo, tree, offset, tokenlen, opcode, TRUE); offset += (int) (next_token - line); line = next_token; /* value */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } proto_tree_add_item (tree, hf_value, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; /* check for "noreply" */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; /* reached CRLF */ } if (tokenlen == 7 && strncmp (line, "noreply", 7) == 0) { proto_tree_add_item (tree, hf_noreply, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; } else { return -1; /* should have been noreply or CRLF. */ } /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; /* CRLF */ } else { /*something's wrong; invalid command maybe. */ return -1; } break; case OP_DELETE: /* key */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return -1; } /* dissect key. */ dissect_key (tvb, pinfo, tree, offset, tokenlen, opcode, TRUE); offset += (int) (next_token - line); line = next_token; /* check if it's expiration or noreply */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; /* neither expiration nor noreply; CRLF */ } if (tokenlen <= 10) { if (tokenlen == 7 && strncmp (line, "noreply", 7) == 0) { /* noreply */ proto_tree_add_item (tree, hf_noreply, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); } else { /* expiration */ memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; expiration = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_expiration, tvb, offset, tokenlen, expiration); } offset += (int) (next_token - line); line = next_token; } else { return -1; } /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; } else { /*something's wrong; invalid command maybe. */ return -1; } break; case OP_GET: case OP_GETS: /* could be followed by any number of keys, add * them one by one. tokenlen cannot be 0 to begin * with. */ while (tokenlen != 0) { tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; /* CRLF */ } dissect_key (tvb, pinfo, tree, offset, tokenlen, opcode, TRUE); offset += (int) (next_token - line); line = next_token; } break; case OP_STAT: tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { /* just the 'stats' command;*/ return offset; } else { /* there is a sub command; record it*/ proto_tree_add_item (tree, hf_subcommand, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); line = next_token; } /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; } else { /* something's wrong; invalid command maybe. */ return -1; } break; case OP_FLUSH: /* check if it's expiration or noreply */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; /* neither expiration nor noreply; CRLF */ } if (tokenlen <= 10) { if (tokenlen == 7 && strncmp (line, "noreply", 7) == 0) { /* noreply */ proto_tree_add_item (tree, hf_noreply, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); } else { /* expiration */ memcpy (response_chars, line, tokenlen); response_chars[tokenlen] = '\0'; expiration = (guint32) strtoul (response_chars, NULL, 10); proto_tree_add_uint (tree, hf_expiration, tvb, offset, tokenlen, expiration); } offset += (int) (next_token - line); line = next_token; } else { return -1; } /* maybe noreply now? */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; } if (tokenlen == 7 && strncmp (line, "noreply", 7) == 0) { /* noreply */ proto_tree_add_item (tree, hf_noreply, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); offset += (int) (next_token - line); } else { return -1; /* expecting CRLF and if not noreply*/ } break; case OP_VERBOSE: /* not implemented for now.*/ break; case OP_VERSION: case OP_QUIT: /* CRLF */ tokenlen = get_token_len (line, lineend, &next_token); if (tokenlen == 0) { return offset; } else { /*something's wrong; invalid command maybe. */ return -1; } default: /* invalid command maybe; break out. */ break; } return offset; } /* * any message that is not starting with the following keywords * is a response. */ static int is_memcache_request_or_reply (const gchar *data, int linelen, guint8 *opcode, memcache_type_t *type, int *expect_content_length, ReqRespDissector *reqresp_dissector) { const guchar *ptr = (const guchar *)data; int is_request_or_response = FALSE; int indx = 0; /* look for a space */ while (indx < linelen) { if (*ptr == ' ') break; ptr++; indx++; } /* is it a response? */ switch (indx) { case 2: if (strncmp (data, "OK", indx) == 0) { *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; case 3: if (strncmp (data, "END", indx) == 0) { *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; case 4: if (strncmp (data, "STAT", indx) == 0) { *opcode = OP_STAT; *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; case 5: if (strncmp (data, "VALUE", indx) == 0) { *opcode = OP_GET; *type = MEMCACHE_RESPONSE; *expect_content_length = TRUE; is_request_or_response = TRUE; } break; case 6: if (strncmp (data, "EXISTS", indx) == 0 || strncmp (data, "STORED", indx) == 0) { *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; case 7: if (strncmp (data, "VERSION", indx) == 0) { *opcode = OP_VERSION; *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } else if (strncmp (data, "DELETED", indx) == 0) { *opcode = OP_DELETE; *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; case 9: if (strncmp (data, "NOT_FOUND", indx) == 0) { *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; case 10: if (strncmp (data, "NOT_STORED", indx) == 0) { *type = MEMCACHE_RESPONSE; is_request_or_response = TRUE; } break; default: break; /* is it a request? */ } if (is_request_or_response && reqresp_dissector) { *reqresp_dissector = memcache_response_dissector; return is_request_or_response; } /* is it a request? */ switch (indx) { case 3: if (strncmp (data, "get", indx) == 0) { *opcode = OP_GET; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } else if (strncmp (data, "set", indx) == 0) { *opcode = OP_SET; *type = MEMCACHE_REQUEST; *expect_content_length = TRUE; is_request_or_response = TRUE; } else if (strncmp (data, "add", indx) == 0) { *opcode = OP_ADD; *type = MEMCACHE_REQUEST; *expect_content_length = TRUE; is_request_or_response = TRUE; } else if (strncmp (data, "cas", indx) == 0) { *opcode = OP_CAS; *type = MEMCACHE_REQUEST; *expect_content_length = TRUE; is_request_or_response = TRUE; } break; case 4: if (strncmp (data, "gets", indx) == 0) { *opcode = OP_GETS; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } else if (strncmp (data, "incr", indx) == 0) { *opcode = OP_INCREMENT; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } else if (strncmp (data, "decr", indx) == 0) { *opcode = OP_DECREMENT; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } else if (strncmp (data, "quit", indx) == 0) { *opcode = OP_QUIT; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } break; case 5: if (strncmp (data, "stats", indx) == 0) { *opcode = OP_STAT; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } break; case 6: if (strncmp (data, "append", indx) == 0) { *opcode = OP_APPEND; *type = MEMCACHE_REQUEST; *expect_content_length = TRUE; is_request_or_response = TRUE; } else if (strncmp (data, "delete", indx) == 0) { *opcode = OP_DELETE; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } break; case 7: if (strncmp (data, "replace", indx) == 0) { *opcode = OP_REPLACE; *type = MEMCACHE_REQUEST; *expect_content_length = TRUE; is_request_or_response = TRUE; } else if (strncmp (data, "prepend", indx) == 0) { *opcode = OP_PREPEND; *type = MEMCACHE_REQUEST; *expect_content_length = TRUE; is_request_or_response = TRUE; } else if (strncmp (data, "version", indx) == 0) { *opcode = OP_VERSION; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } break; case 9: if (strncmp (data, "flush_all", indx) == 0) { *opcode = OP_FLUSH; *type = MEMCACHE_REQUEST; is_request_or_response = TRUE; } break; default: break; /* check if it is an 'incr' or 'stats sizes' response. */ } if (is_request_or_response && reqresp_dissector) { *reqresp_dissector = memcache_request_dissector; return is_request_or_response; } /* XXX: * Recognize 'incr', 'decr' and 'stats sizes' responses. * I don't have a solution for this yet. */ return is_request_or_response; } /* dissect memcache textual protocol PDUs. */ static void dissect_memcache_text (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { int offset = 0; int len; while (tvb_reported_length_remaining (tvb, offset) != 0) { /* dissect the memcache packet. */ len = dissect_memcache_message (tvb, offset, pinfo, tree); if (len == -1) break; offset += len; /* * OK, we've set the Protocol and Info columns for the * first MEMCACHE message; set a fence so that subsequent * MEMCACHE messages don't overwrite the Info column. */ col_set_fence (pinfo->cinfo, COL_INFO); } } /* Dissect tcp packets based on the type of protocol (text/binary) */ static int dissect_memcache_tcp (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) { gint offset = 0; guint8 magic; magic = tvb_get_guint8 (tvb, offset); if (try_val_to_str (magic, magic_vals) != NULL) { tcp_dissect_pdus (tvb, pinfo, tree, memcache_desegment_body, 12, get_memcache_pdu_len, dissect_memcache, data); } else { dissect_memcache_text (tvb, pinfo, tree); } return tvb_captured_length(tvb); } /* Dissect udp packets based on the type of protocol (text/binary) */ static int dissect_memcache_udp (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) { gint offset = 0; guint8 magic; magic = tvb_get_guint8 (tvb, offset); if (try_val_to_str (magic, magic_vals) != NULL) { dissect_memcache (tvb, pinfo, tree, data); } else { dissect_memcache_message (tvb, 0, pinfo, tree); } return tvb_captured_length(tvb); } /* Registration functions; register memcache protocol, * its configuration options and also register the tcp and udp * dissectors. */ void proto_register_memcache (void) { static hf_register_info hf[] = { { &hf_magic, { "Magic", "memcache.magic", FT_UINT8, BASE_DEC, VALS (magic_vals), 0x0, "Magic number", HFILL } }, { &hf_opcode, { "Opcode", "memcache.opcode", FT_UINT8, BASE_DEC, VALS (opcode_vals), 0x0, "Command code", HFILL } }, { &hf_extras_length, { "Extras length", "memcache.extras.length", FT_UINT8, BASE_DEC, NULL, 0x0, "Length in bytes of the command extras", HFILL } }, { &hf_key_length, { "Key Length", "memcache.key.length", FT_UINT16, BASE_DEC, NULL, 0x0, "Length in bytes of the text key that follows the command extras", HFILL } }, { &hf_value_length, { "Value length", "memcache.value.length", FT_UINT32, BASE_DEC, NULL, 0x0, "Length in bytes of the value that follows the key", HFILL } }, { &hf_data_type, { "Data type", "memcache.data_type", FT_UINT8, BASE_DEC, VALS (data_type_vals), 0x0, NULL, HFILL } }, { &hf_reserved, { "Reserved", "memcache.reserved", FT_UINT16, BASE_DEC, NULL, 0x0, "Reserved for future use", HFILL } }, { &hf_status, { "Status", "memcache.status", FT_UINT16, BASE_DEC, VALS (status_vals), 0x0, "Status of the response", HFILL } }, { &hf_total_body_length, { "Total body length", "memcache.total_body_length", FT_UINT32, BASE_DEC, NULL, 0x0, "Length in bytes of extra + key + value", HFILL } }, { &hf_opaque, { "Opaque", "memcache.opaque", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_cas, { "CAS", "memcache.cas", FT_UINT64, BASE_DEC, NULL, 0x0, "Data version check", HFILL } }, { &hf_extras, { "Extras", "memcache.extras", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_extras_flags, { "Flags", "memcache.extras.flags", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_extras_expiration, { "Expiration", "memcache.extras.expiration", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_extras_delta, { "Amount to add", "memcache.extras.delta", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_extras_initial, { "Initial value", "memcache.extras.initial", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_extras_unknown, { "Unknown", "memcache.extras.unknown", FT_BYTES, BASE_NONE, NULL, 0x0, "Unknown Extras", HFILL } }, { &hf_key, { "Key", "memcache.key", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_value, { "Value", "memcache.value", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_uint64_response, { "Response", "memcache.extras.response", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_command, { "Command", "memcache.command", FT_STRING, BASE_NONE , NULL, 0x0, NULL, HFILL } }, { &hf_subcommand, { "Sub command", "memcache.subcommand", FT_STRING, BASE_NONE, NULL, 0x0, "Sub command if any", HFILL } }, { &hf_flags, { "Flags", "memcache.flags", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_expiration, { "Expiration", "memcache.expiration", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_noreply, { "Noreply", "memcache.noreply", FT_STRING, BASE_NONE, NULL, 0x0, "Client does not expect a reply", HFILL } }, { &hf_response, { "Response", "memcache.response", FT_STRING, BASE_NONE, NULL, 0x0, "Response command", HFILL } }, { &hf_version, { "Version", "memcache.version", FT_STRING, BASE_NONE, NULL, 0x0, "Version of running memcache", HFILL } }, { &hf_slabclass, { "Slab class", "memcache.slabclass", FT_UINT32, BASE_DEC, NULL, 0x0, "Slab class of a stat", HFILL } }, { &hf_name, { "Stat name", "memcache.name", FT_STRING, BASE_NONE, NULL, 0x0, "Name of a stat", HFILL } }, { &hf_name_value, { "Stat value", "memcache.name_value", FT_STRING, BASE_NONE, NULL, 0x0, "Value of a stat", HFILL } }, }; static gint *ett[] = { &ett_memcache, &ett_extras }; static ei_register_info ei[] = { { &ei_extras_unknown, { "memcache.extras.notexpected", PI_UNDECODED, PI_WARN, "shall not have Extras", EXPFILL }}, { &ei_extras_missing, { "memcache.extras.missing", PI_UNDECODED, PI_WARN, "must have Extras", EXPFILL }}, { &ei_key_unknown, { "memcache.key.notexpected", PI_UNDECODED, PI_WARN, "shall not have Key", EXPFILL }}, { &ei_key_missing, { "memcache.key.missing", PI_UNDECODED, PI_WARN, "must have Key", EXPFILL }}, { &ei_value_length, { "memcache.value.invalid", PI_UNDECODED, PI_WARN, "Illegal Value length, should be 8", EXPFILL }}, { &ei_value_unknown, { "memcache.value.notexpected", PI_UNDECODED, PI_WARN, "shall not have Value", EXPFILL }}, { &ei_value_missing, { "memcache.value.missing", PI_UNDECODED, PI_WARN, "must have Value", EXPFILL }}, { &ei_magic_unknown, { "memcache.magic.unknown", PI_UNDECODED, PI_WARN, "Unknown magic byte", EXPFILL }}, { &ei_opcode_unknown, { "memcache.opcode.unknown", PI_UNDECODED, PI_WARN, "Unknown opcode", EXPFILL }}, { &ei_status_response, { "memcache.status.response", PI_RESPONSE_CODE, PI_NOTE, "Error response", EXPFILL }}, { &ei_reserved_value, { "memcache.reserved.expert", PI_UNDECODED, PI_WARN, "Reserved value", EXPFILL }}, }; module_t *memcache_module; expert_module_t *expert_memcache; proto_memcache = proto_register_protocol (PNAME, PSNAME, PFNAME); memcache_tcp_handle = register_dissector ("memcache.tcp", dissect_memcache_tcp, proto_memcache); memcache_udp_handle = register_dissector ("memcache.udp", dissect_memcache_udp, proto_memcache); proto_register_field_array (proto_memcache, hf, array_length (hf)); proto_register_subtree_array (ett, array_length (ett)); expert_memcache = expert_register_protocol(proto_memcache); expert_register_field_array(expert_memcache, ei, array_length(ei)); /* Register our configuration options */ memcache_module = prefs_register_protocol (proto_memcache, proto_reg_handoff_memcache); prefs_register_bool_preference (memcache_module, "desegment_headers", "Reassemble MEMCACHE headers spanning multiple TCP segments", "Whether the MEMCACHE dissector should reassemble headers " "of a request spanning multiple TCP segments. " "To use this option, you must also enable " "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", &memcache_desegment_headers); prefs_register_bool_preference (memcache_module, "desegment_pdus", "Reassemble PDUs spanning multiple TCP segments", "Whether the memcache dissector should reassemble PDUs" " spanning multiple TCP segments." " To use this option, you must also enable \"Allow subdissectors" " to reassemble TCP streams\" in the TCP protocol settings.", &memcache_desegment_body); range_convert_str(&memcache_tcp_port_range, MEMCACHE_DEFAULT_RANGE, 65535); range_convert_str(&memcache_udp_port_range, MEMCACHE_DEFAULT_RANGE, 65535); prefs_register_range_preference(memcache_module, "tcp.ports", \ "MEMCACHE TCP Port range", \ "MEMCACHE TCP Port range", \ &memcache_tcp_port_range, \ 65535); prefs_register_range_preference(memcache_module, "udp.ports", \ "MEMCACHE UDP Port range", \ "MEMCACHE UDP Port range", \ &memcache_udp_port_range, \ 65535); } /* Register the tcp and udp memcache dissectors. */ void proto_reg_handoff_memcache (void) { static range_t *orig_memcache_tcp_port_range = NULL; static range_t *orig_memcache_udp_port_range = NULL; dissector_delete_uint_range("tcp.port", orig_memcache_tcp_port_range, memcache_tcp_handle); dissector_delete_uint_range("udp.port", orig_memcache_udp_port_range, memcache_udp_handle); g_free(orig_memcache_tcp_port_range); g_free(orig_memcache_udp_port_range); orig_memcache_tcp_port_range = range_copy(memcache_tcp_port_range); orig_memcache_udp_port_range = range_copy(memcache_udp_port_range); dissector_add_uint_range("tcp.port", orig_memcache_tcp_port_range, memcache_tcp_handle); dissector_add_uint_range("udp.port", orig_memcache_udp_port_range, memcache_udp_handle); } /* * Editor modelines * * Local Variables: * c-basic-offset: 2 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=2 tabstop=8 expandtab: * :indentSize=2:tabSize=8:noTabs=true: */