/* packet-memcache.c * Routines for Memcache Binary Protocol * http://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol * * Copyright 2009, Stig Bjorlykke * * $Id$ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "packet-tcp.h" #define PNAME "Memcache Binary Protocol" #define PSNAME "MEMCACHE" #define PFNAME "memcache" #define MEMCACHE_PORT 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 /* Data Types */ #define DT_RAW_BYTES 0x00 static int proto_memcache = -1; 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_extras_missing = -1; static int hf_key = -1; static int hf_key_missing = -1; static int hf_value = -1; static int hf_value_missing = -1; static int hf_uint64_response = -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" }, { 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" }, { 0, NULL } }; static const value_string data_type_vals[] = { { DT_RAW_BYTES, "Raw bytes" }, { 0, NULL } }; static guint get_memcache_pdu_len (packet_info *pinfo _U_, tvbuff_t *tvb, int offset) { 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, FALSE); 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, FALSE); 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, FALSE); offset += 4; proto_tree_add_item (extras_tree, hf_extras_expiration, tvb, offset, 4, FALSE); 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, FALSE); offset += 8; proto_tree_add_item (extras_tree, hf_extras_initial, tvb, offset, 8, FALSE); offset += 8; proto_tree_add_item (extras_tree, hf_extras_expiration, tvb, offset, 4, FALSE); 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, FALSE); 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, FALSE); offset += extras_len; } break; } if (illegal) { ti = proto_tree_add_item (extras_tree, hf_extras_unknown, tvb, offset, extras_len, FALSE); expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "%s %s shall not have Extras", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); 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", 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); } } static void dissect_key (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, guint16 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, FALSE); offset += key_len; } /* Sanity check */ if (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, PI_UNDECODED, PI_WARN, "%s %s shall not have Key", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } else if (missing) { ti = proto_tree_add_item (tree, hf_key_missing, tvb, offset, 0, FALSE); expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "%s %s must have Key", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } } 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, FALSE); if (value_len != 8) { expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "Illegal Value length, should be 8"); } } else { ti = proto_tree_add_item (tree, hf_value, tvb, offset, value_len, FALSE); } 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, PI_UNDECODED, PI_WARN, "%s %s shall not have Value", val_to_str (opcode, opcode_vals, "Opcode %d"), request ? "Request" : "Response"); } else if (missing) { 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"); } } static void dissect_memcache (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { 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; 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); memcache_item = proto_tree_add_item (tree, proto_memcache, tvb, offset, -1, FALSE); 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, FALSE); offset += 1; if (match_strval (magic, magic_vals) == NULL) { expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "Unknown magic byte: %d", magic); } opcode = tvb_get_guint8 (tvb, offset); ti = proto_tree_add_item (memcache_tree, hf_opcode, tvb, offset, 1, FALSE); offset += 1; if (match_strval (opcode, opcode_vals) == NULL) { expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "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)")); 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)")); key_len = tvb_get_ntohs (tvb, offset); proto_tree_add_item (memcache_tree, hf_key_length, tvb, offset, 2, FALSE); offset += 2; extras_len = tvb_get_guint8 (tvb, offset); proto_tree_add_item (memcache_tree, hf_extras_length, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item (memcache_tree, hf_data_type, tvb, offset, 1, FALSE); 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, FALSE); if (status != 0) { expert_add_info_format (pinfo, ti, PI_RESPONSE_CODE, PI_NOTE, "%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, FALSE); if (status != 0) { expert_add_info_format (pinfo, ti, PI_UNDECODED, PI_WARN, "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, FALSE); offset += 4; proto_tree_add_item (memcache_tree, hf_opaque, tvb, offset, 4, FALSE); offset += 4; proto_tree_add_item (memcache_tree, hf_cas, tvb, offset, 8, FALSE); 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, 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")); } 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", val_to_str (opcode, opcode_vals, "Opcode %d"), val_to_str (status, status_vals, "Unknown"), status); } } 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); } static void dissect_memcache_udp (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { dissect_memcache (tvb, pinfo, tree); } 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_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 } }, { &hf_key, { "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 } }, { &hf_value, { "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 } }, }; static gint *ett[] = { &ett_memcache, &ett_extras }; module_t *memcache_module; proto_memcache = proto_register_protocol (PNAME, PSNAME, PFNAME); register_dissector ("memcache.tcp", dissect_memcache_tcp, proto_memcache); 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)); /* Register our configuration options */ memcache_module = prefs_register_protocol (proto_memcache, NULL); 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); } void proto_reg_handoff_memcache (void) { dissector_handle_t memcache_tcp_handle; dissector_handle_t memcache_udp_handle; memcache_tcp_handle = find_dissector ("memcache.tcp"); memcache_udp_handle = find_dissector ("memcache.udp"); dissector_add ("tcp.port", MEMCACHE_PORT, memcache_tcp_handle); dissector_add ("udp.port", MEMCACHE_PORT, 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 noexpandtab * :indentSize=2:tabSize=8:noTabs=false: */