aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-memcache.c
diff options
context:
space:
mode:
authorStig Bjørlykke <stig@bjorlykke.org>2009-05-24 22:39:07 +0000
committerStig Bjørlykke <stig@bjorlykke.org>2009-05-24 22:39:07 +0000
commit905b8d0b2db72e8f1dc37c86fa2f0840761a0e2e (patch)
tree1a67fe838dc0486e6afae2d60971e0c5f3669c93 /epan/dissectors/packet-memcache.c
parentb787288d49fe455f6974b17b468565dd5119aad2 (diff)
From Rama Chitta (bug 3467):
Added routines for Memcache Textual Protocol. svn path=/trunk/; revision=28462
Diffstat (limited to 'epan/dissectors/packet-memcache.c')
-rw-r--r--epan/dissectors/packet-memcache.c1629
1 files changed, 1573 insertions, 56 deletions
diff --git a/epan/dissectors/packet-memcache.c b/epan/dissectors/packet-memcache.c
index b0738463b7..133c9c7eff 100644
--- a/epan/dissectors/packet-memcache.c
+++ b/epan/dissectors/packet-memcache.c
@@ -4,6 +4,11 @@
*
* Copyright 2009, Stig Bjorlykke <stig@bjorlykke.org>
*
+ * Routines for Memcache Textual Protocol
+ * http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
+ *
+ * Copyright 2009, Rama Chitta <rama@gear6.com>
+ *
* $Id$
*
* Wireshark - Network traffic analyzer
@@ -29,13 +34,26 @@
#include "config.h"
#endif
+#include <string.h>
+#include <ctype.h>
+
+#include <glib.h>
+#include <epan/conversation.h>
+#include <epan/packet.h>
+#include <epan/strutil.h>
+#include <epan/base64.h>
+#include <epan/emem.h>
+#include <epan/stats_tree.h>
+
+#include <epan/req_resp_hdrs.h>
+
#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/expert.h>
#include "packet-tcp.h"
-#define PNAME "Memcache Binary Protocol"
+#define PNAME "Memcache Protocol"
#define PSNAME "MEMCACHE"
#define PFNAME "memcache"
@@ -85,6 +103,12 @@
#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
@@ -114,12 +138,22 @@ static int hf_value = -1;
static int hf_value_missing = -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;
-/* User definable values */
-static gboolean memcache_desegment = TRUE;
-
static const value_string magic_vals[] = {
{ MAGIC_REQUEST, "Request" },
{ MAGIC_RESPONSE, "Response" },
@@ -166,6 +200,7 @@ static const value_string opcode_vals[] = {
{ OP_FLUSH_Q, "Flush Quietly" },
{ OP_APPEND_Q, "Append Quietly" },
{ OP_PREPEND_Q, "Prepend Quietly" },
+ /* Internally defined values not valid here */
{ 0, NULL }
};
@@ -174,6 +209,32 @@ static const value_string data_type_vals[] = {
{ 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)
{
@@ -231,7 +292,7 @@ dissect_extras (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
if (request) {
proto_tree_add_item (extras_tree, hf_extras_flags, tvb, offset, 4, FALSE);
offset += 4;
-
+
proto_tree_add_item (extras_tree, hf_extras_expiration, tvb, offset, 4, FALSE);
offset += 4;
} else {
@@ -309,13 +370,14 @@ dissect_extras (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
offset += extras_len;
} else if (missing) {
ti = proto_tree_add_item (tree, hf_extras_missing, tvb, offset, 0, FALSE);
- expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "%s %s must have Extras",
+ expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "%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, PI_UNDECODED, PI_WARN, "Illegal Extras length, should be %d", offset - save_offset);
+ expert_add_info_format (pinfo, extras_item, PI_UNDECODED, PI_WARN,
+ "Illegal Extras length, should be %d", offset - save_offset);
}
}
@@ -439,7 +501,7 @@ dissect_value (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
ti = proto_tree_add_item (tree, hf_value_missing, tvb, offset, 0, FALSE);
expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "%s %s must have Value",
val_to_str (opcode, opcode_vals, "Opcode %d"),
- request ? "Request" : "Response");
+ request ? "Request" : "Response");
}
}
@@ -454,11 +516,8 @@ dissect_memcache (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
guint32 body_len, value_len;
gboolean request;
- if (check_col (pinfo->cinfo, COL_PROTOCOL))
- col_set_str (pinfo->cinfo, COL_PROTOCOL, PSNAME);
-
- if (check_col (pinfo->cinfo, COL_INFO))
- col_clear (pinfo->cinfo, COL_INFO);
+ 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, FALSE);
memcache_tree = proto_item_add_subtree (memcache_item, ett_memcache);
@@ -482,10 +541,9 @@ dissect_memcache (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
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)"));
- if (check_col (pinfo->cinfo, COL_INFO))
- 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)"));
+ 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, FALSE);
@@ -542,10 +600,9 @@ dissect_memcache (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
} else if (body_len) {
proto_tree_add_item (memcache_tree, hf_value, tvb, offset, body_len, FALSE);
offset += body_len;
-
- if (check_col (pinfo->cinfo, COL_INFO))
- col_append_fstr (pinfo->cinfo, COL_INFO, " (%s)",
- val_to_str (status, status_vals, "Unknown status: %d"));
+
+ col_append_fstr (pinfo->cinfo, COL_INFO, " (%s)",
+ val_to_str (status, status_vals, "Unknown status: %d"));
} else {
ti = proto_tree_add_item (memcache_tree, hf_value_missing, tvb, offset, 0, FALSE);
expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "%s with status %s (%d) must have Value",
@@ -554,69 +611,1520 @@ dissect_memcache (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
}
}
+/* 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;
+ gchar *bytes_val;
+ int tokenlen, i = 0, linelen;
+ gint next_offset;
+
+ /* get the header line. */
+ linelen = tvb_find_line_end (tvb, offset,
+ tvb_ensure_length_remaining (tvb, offset), &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_ephemeral_string (tvb, offset, tokenlen);
+ 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_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.
+ */
+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_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:
+ * <command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
+ * cas <key> <flags> <exptime> <bytes> <cas unqiue> [noreply]\r\n
+ *
+ * response:
+ * VALUE <key> <flags> <bytes> [<cas unique>]\r\n
+ * <data block>\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,
+ tvb_ensure_length_remaining (tvb, offset), &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, FALSE);
+ memcache_tree = proto_item_add_subtree (memcache_item, ett_memcache);
+
+ /* Process the packet data. The first line is expected to be a
+ * header. If its 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) {
+ if (tree) {
+ 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_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
+ * <data block>\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;
+ int len;
+ 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 ((len = tvb_reported_length_remaining (tvb, offset) != 0)) {
+ /* bytes actually remaining in this tvbuff. */
+ datalen = tvb_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, len;
+ const guchar *line, *lineend;
+
+ const guchar *next_token;
+ int tokenlen;
+
+ /* expecting to read 'bytes' number of bytes from the buffer. */
+ if ((len = tvb_reported_length_remaining (tvb, offset)) != 0) {
+ /* Find the end of the line. */
+ linelen = tvb_find_line_end (tvb, offset,
+ tvb_ensure_length_remaining (tvb, offset), &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, FALSE);
+ offset += (int) (next_token - line);
+ line = next_token;
+
+ /* 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;
+ int reported_datalen = -1;
+ guint32 slabclass;
+ guchar response_chars[21];
+
+ while ((reported_datalen = tvb_reported_length_remaining (tvb, offset)) != 0) {
+ /* Find the end of the line. */
+ linelen = tvb_find_line_end (tvb, offset,
+ tvb_ensure_length_remaining (tvb, offset), &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, FALSE);
+ 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, FALSE);
+ 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;
+ }
+
+ /* <hf_name> <hf_name_value>\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, FALSE);
+ 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, FALSE);
+ offset += (int) (next_token - line);
+ line = next_token;
+
+ offset = next_offset;
+ occurrences = 0;
+ }
+
+ 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, len;
+ 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 ((len = tvb_reported_length_remaining (tvb, offset)) != 0) {
+ /* Find the end of the line. */
+ linelen = tvb_find_line_end (tvb, offset,
+ tvb_ensure_length_remaining (tvb, offset), &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_uint (tree, hf_cas, tvb, offset, tokenlen, cas);
+
+ offset += (int) (next_token - line);
+ line = next_token;
+
+ /* CRLF */
+ tokenlen = get_token_len (line, lineend, &next_token);
+ if (tokenlen != 0) {
+ return -1; /* invalid token */
+ }
+ }
+
+ offset = next_offset;
+ /* <datablock>\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, FALSE);
+ offset += (int) (next_token - line);
+ line = next_token;
+
+ /* CRLF */
+ tokenlen = get_token_len (line, lineend, &next_token);
+ if (tokenlen == 0) {
+ return offset;
+ } else {
+ /* 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, FALSE);
+ offset += (int) (next_token - line);
+ line = next_token;
+ return offset;
+ }
+
+ /* if we have reached this point:
+ * it is either an incr/decr response of the format
+ * <value>\r\n.
+ * or
+ * "stats sizes" response of the format:
+ * <size> <count> \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, FALSE);
+ 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_uint (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, FALSE);
+ }
+ offset += (int) (next_token - line);
+ line = next_token;
+ }
+
+ offset += 2 ; /* go past /r/n*/
+ /* <datablock>\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, FALSE);
+ 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, FALSE);
+ 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 its 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, FALSE);
+ } 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, FALSE);
+ 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 its 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, FALSE);
+ } 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, FALSE);
+ offset += (int) (next_token - line);
+ line = next_token;
+ } 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 void
dissect_memcache_tcp (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
-{
- tcp_dissect_pdus (tvb, pinfo, tree, memcache_desegment, 12,
- get_memcache_pdu_len, dissect_memcache);
+{
+ gint offset = 0;
+ guint8 magic;
+
+ magic = tvb_get_guint8 (tvb, offset);
+
+ if (match_strval (magic, magic_vals) != NULL) {
+ tcp_dissect_pdus (tvb, pinfo, tree, memcache_desegment_body, 12,
+ get_memcache_pdu_len, dissect_memcache);
+ } else {
+ dissect_memcache_text (tvb, pinfo, tree);
+ }
}
+/* Dissect udp packets based on the type of protocol (text/binary) */
static void
dissect_memcache_udp (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
- dissect_memcache (tvb, pinfo, tree);
+ gint offset = 0;
+ guint8 magic;
+
+ magic = tvb_get_guint8 (tvb, offset);
+
+ if (match_strval (magic, magic_vals) != NULL) {
+ dissect_memcache (tvb, pinfo, tree);
+ } else {
+ dissect_memcache_message (tvb, 0, pinfo, tree);
+ }
}
+/* 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[] = {
+{
+ static hf_register_info hf[] = {
{ &hf_magic,
- { "Magic", "memcache.magic", FT_UINT8, BASE_DEC, VALS(magic_vals), 0x0, "Magic number", HFILL } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "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 } },
+ { "Initial value", "memcache.extras.initial",
+ FT_UINT64, BASE_DEC, NULL, 0x0,
+ NULL, HFILL } },
+
{ &hf_extras_unknown,
- { "Unknown", "memcache.extras.unknown", FT_BYTES, BASE_DEC, NULL, 0x0, "Unknown Extras", HFILL } },
+ { "Unknown", "memcache.extras.unknown",
+ FT_BYTES, BASE_DEC, NULL, 0x0,
+ "Unknown Extras", HFILL } },
+
{ &hf_extras_missing,
- { "Extras missing", "memcache.extras.missing", FT_NONE, BASE_NONE, NULL, 0x0, "Extras is mandatory for this command", HFILL } },
+ { "Extras missing", "memcache.extras.missing",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "Extras is mandatory for this command", HFILL } },
+
{ &hf_key,
- { "Key", "memcache.key", FT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { "Key", "memcache.key",
+ FT_STRING, BASE_DEC, NULL, 0x0,
+ NULL, HFILL } },
+
{ &hf_key_missing,
- { "Key missing", "memcache.key.missing", FT_NONE, BASE_NONE, NULL, 0x0, "Key is mandatory for this command", HFILL } },
+ { "Key missing", "memcache.key.missing",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "Key is mandatory for this command", HFILL } },
+
{ &hf_value,
- { "Value", "memcache.value", FT_STRING, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { "Value", "memcache.value",
+ FT_STRING, BASE_DEC, NULL, 0x0,
+ NULL, HFILL } },
+
{ &hf_value_missing,
- { "Value missing", "memcache.value.missing", FT_NONE, BASE_NONE, NULL, 0x0, "Value is mandatory for this command", HFILL } },
- { &hf_uint64_response,
- { "Response", "memcache.extras.response", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
+ { "Value missing", "memcache.value.missing",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "Value is mandatory for this command", 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[] = {
@@ -636,15 +2144,24 @@ proto_register_memcache (void)
/* Register our configuration options */
memcache_module = prefs_register_protocol (proto_memcache, NULL);
- prefs_register_bool_preference (memcache_module, "desegment_pdus",
+ 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);
+ &memcache_desegment_body);
}
+/* Register the tcp and udp memcache dissectors. */
void
proto_reg_handoff_memcache (void)
{
@@ -663,10 +2180,10 @@ proto_reg_handoff_memcache (void)
*
* Local Variables:
* c-basic-offset: 2
- * tab-width: 8
+ * tab-width: 2
* indent-tabs-mode: nil
* End:
*
- * ex: set shiftwidth=2 tabstop=8 noexpandtab
- * :indentSize=2:tabSize=8:noTabs=false:
+ * ex: set shiftwidth=2 tabstop=2 noexpandtab
+ * :indentSize=2:tabSize=2:noTabs=false:
*/