Chromium Code Reviews| Index: net/tools/spdyshark/packet-spdy.c |
| diff --git a/net/tools/spdyshark/packet-spdy.c b/net/tools/spdyshark/packet-spdy.c |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4b48596fd5eb770fd04c97258f7132b22b00d115 |
| --- /dev/null |
| +++ b/net/tools/spdyshark/packet-spdy.c |
| @@ -0,0 +1,1464 @@ |
| +/* packet-spdy.c |
| + * Routines for SPDY packet disassembly |
| + * For now, the protocol spec can be found at |
| + * http://dev.chromium.org/spdy/spdy-protocol |
| + * |
| + * Copyright 2010, Google Inc. |
| + * Eric Shienbrood <ers@google.com> |
| + * |
| + * $Id$ |
| + * |
| + * Wireshark - Network traffic analyzer |
| + * By Gerald Combs <gerald@wireshark.org> |
| + * Copyright 1998 Gerald Combs |
| + * |
| + * Originally based on packet-http.c |
| + * |
| + * 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 <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 <plugins/spdy/packet-spdy.h> |
| +#include <epan/dissectors/packet-tcp.h> |
| +#include <epan/dissectors/packet-ssl.h> |
| +#include <epan/prefs.h> |
| +#include <epan/expert.h> |
| +#include <epan/uat.h> |
| + |
| +#define SPDY_FIN 0x01 |
| + |
| +/* The types of SPDY control frames */ |
| +typedef enum _spdy_type { |
| + SPDY_DATA, |
| + SPDY_SYN_STREAM, |
| + SPDY_SYN_REPLY, |
| + SPDY_FIN_STREAM, |
| + SPDY_HELLO, |
| + SPDY_NOOP, |
| + SPDY_PING, |
| + SPDY_INVALID |
| +} spdy_frame_type_t; |
| + |
| +static const char *frame_type_names[] = { |
| + "DATA", "SYN_STREAM", "SYN_REPLY", "FIN_STREAM", "HELLO", "NOOP", |
| + "PING", "INVALID" |
| +}; |
| + |
| +/* |
| + * This structure will be tied to each SPDY frame. |
| + * Note that there may be multiple SPDY frames |
| + * in one packet. |
| + */ |
| +typedef struct _spdy_frame_info_t { |
| + guint32 stream_id; |
| + guint8 *header_block; |
| + guint header_block_len; |
| + guint16 frame_type; |
| +} spdy_frame_info_t; |
| + |
| +/* |
| + * This structures keeps track of all the data frames |
| + * associated with a stream, so that they can be |
| + * reassembled into a single chunk. |
| + */ |
| +typedef struct _spdy_data_frame_t { |
| + guint8 *data; |
| + guint32 length; |
| + guint32 framenum; |
| +} spdy_data_frame_t; |
| + |
| +typedef struct _spdy_stream_info_t { |
| + gchar *content_type; |
| + gchar *content_type_parameters; |
| + gchar *content_encoding; |
| + GSList *data_frames; |
| + tvbuff_t *assembled_data; |
| + guint num_data_frames; |
| +} spdy_stream_info_t; |
| + |
| +#include <epan/tap.h> |
| + |
| + |
| +static int spdy_tap = -1; |
| +static int spdy_eo_tap = -1; |
| + |
| +static int proto_spdy = -1; |
| +static int hf_spdy_syn_stream = -1; |
| +static int hf_spdy_syn_reply = -1; |
| +static int hf_spdy_control_bit = -1; |
| +static int hf_spdy_version = -1; |
| +static int hf_spdy_type = -1; |
| +static int hf_spdy_flags = -1; |
| +static int hf_spdy_flags_fin = -1; |
| +static int hf_spdy_length = -1; |
| +static int hf_spdy_header = -1; |
| +static int hf_spdy_header_name = -1; |
| +static int hf_spdy_header_name_text = -1; |
| +static int hf_spdy_header_value = -1; |
| +static int hf_spdy_header_value_text = -1; |
| +static int hf_spdy_streamid = -1; |
| +static int hf_spdy_priority = -1; |
| +static int hf_spdy_num_headers = -1; |
| +static int hf_spdy_num_headers_string = -1; |
| + |
| +static gint ett_spdy = -1; |
| +static gint ett_spdy_syn_stream = -1; |
| +static gint ett_spdy_syn_reply = -1; |
| +static gint ett_spdy_fin_stream = -1; |
| +static gint ett_spdy_flags = -1; |
| +static gint ett_spdy_header = -1; |
| +static gint ett_spdy_header_name = -1; |
| +static gint ett_spdy_header_value = -1; |
| + |
| +static gint ett_spdy_encoded_entity = -1; |
| + |
| +static dissector_handle_t data_handle; |
| +static dissector_handle_t media_handle; |
| +static dissector_handle_t spdy_handle; |
| + |
| +/* Stuff for generation/handling of fields for custom HTTP headers */ |
| +typedef struct _header_field_t { |
| + gchar* header_name; |
| + gchar* header_desc; |
| +} header_field_t; |
| + |
| +/* |
| + * desegmentation of SPDY control frames |
| + * (when we are over TCP or another protocol providing the desegmentation API) |
| + */ |
| +static gboolean spdy_desegment_control_frames = TRUE; |
| + |
| +/* |
| + * desegmentation of SPDY data frames bodies |
| + * (when we are over TCP or another protocol providing the desegmentation API) |
| + * TODO let the user filter on content-type the bodies he wants desegmented |
| + */ |
| +static gboolean spdy_desegment_data_frames = TRUE; |
| + |
| +static gboolean spdy_assemble_entity_bodies = TRUE; |
| + |
| +/* |
| + * Decompression of zlib encoded entities. |
| + */ |
| +#ifdef HAVE_LIBZ |
| +static gboolean spdy_decompress_body = TRUE; |
| +static gboolean spdy_decompress_headers = TRUE; |
| +#else |
| +static gboolean spdy_decompress_body = FALSE; |
| +static gboolean spdy_decompress_headers = FALSE; |
| +#endif |
| +static gboolean spdy_debug = FALSE; |
| + |
| +#define TCP_PORT_DAAP 3689 |
|
Mike Belshe
2010/02/22 21:55:08
Remove?
|
| + |
| +/* |
| + * SSDP is implemented atop HTTP (yes, it really *does* run over UDP). |
| + */ |
| +#define TCP_PORT_SSDP 1900 |
|
Mike Belshe
2010/02/22 21:55:08
Remove?
|
| +#define UDP_PORT_SSDP 1900 |
| + |
| +/* |
| + * tcp and ssl ports |
| + */ |
| + |
| +#define TCP_DEFAULT_RANGE "80,8080" |
| +#define SSL_DEFAULT_RANGE "443" |
| + |
| +static range_t *global_spdy_tcp_range = NULL; |
| +static range_t *global_spdy_ssl_range = NULL; |
| + |
| +static range_t *spdy_tcp_range = NULL; |
| +static range_t *spdy_ssl_range = NULL; |
| + |
| +static const value_string vals_status_code[] = { |
| + { 100, "Continue" }, |
| + { 101, "Switching Protocols" }, |
| + { 102, "Processing" }, |
| + { 199, "Informational - Others" }, |
| + |
| + { 200, "OK"}, |
| + { 201, "Created"}, |
| + { 202, "Accepted"}, |
| + { 203, "Non-authoritative Information"}, |
| + { 204, "No Content"}, |
| + { 205, "Reset Content"}, |
| + { 206, "Partial Content"}, |
| + { 207, "Multi-Status"}, |
| + { 299, "Success - Others"}, |
| + |
| + { 300, "Multiple Choices"}, |
| + { 301, "Moved Permanently"}, |
| + { 302, "Found"}, |
| + { 303, "See Other"}, |
| + { 304, "Not Modified"}, |
| + { 305, "Use Proxy"}, |
| + { 307, "Temporary Redirect"}, |
| + { 399, "Redirection - Others"}, |
| + |
| + { 400, "Bad Request"}, |
| + { 401, "Unauthorized"}, |
| + { 402, "Payment Required"}, |
| + { 403, "Forbidden"}, |
| + { 404, "Not Found"}, |
| + { 405, "Method Not Allowed"}, |
| + { 406, "Not Acceptable"}, |
| + { 407, "Proxy Authentication Required"}, |
| + { 408, "Request Time-out"}, |
| + { 409, "Conflict"}, |
| + { 410, "Gone"}, |
| + { 411, "Length Required"}, |
| + { 412, "Precondition Failed"}, |
| + { 413, "Request Entity Too Large"}, |
| + { 414, "Request-URI Too Long"}, |
| + { 415, "Unsupported Media Type"}, |
| + { 416, "Requested Range Not Satisfiable"}, |
| + { 417, "Expectation Failed"}, |
| + { 418, "I'm a teapot"}, /* RFC 2324 */ |
| + { 422, "Unprocessable Entity"}, |
| + { 423, "Locked"}, |
| + { 424, "Failed Dependency"}, |
| + { 499, "Client Error - Others"}, |
| + |
| + { 500, "Internal Server Error"}, |
| + { 501, "Not Implemented"}, |
| + { 502, "Bad Gateway"}, |
| + { 503, "Service Unavailable"}, |
| + { 504, "Gateway Time-out"}, |
| + { 505, "HTTP Version not supported"}, |
| + { 507, "Insufficient Storage"}, |
| + { 599, "Server Error - Others"}, |
| + |
| + { 0, NULL} |
| +}; |
| + |
| +static const char spdy_dictionary[] = |
| + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" |
| + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" |
| + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" |
| + "-agent10010120020120220320420520630030130230330430530630740040140240340440" |
| + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" |
| + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" |
| + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" |
| + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" |
| + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" |
| + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" |
| + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" |
| + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" |
| + ".1statusversionurl"; |
| + |
| +static void reset_decompressors(void) |
| +{ |
| + if (spdy_debug) printf("Should reset SPDY decompressors\n"); |
|
Mike Belshe
2010/02/22 21:55:08
nit: 2 lines
Eric Shienbrood
2010/02/22 23:30:32
C++ style guide says:
"Short conditional statement
|
| +} |
| + |
| +static spdy_conv_t * |
| +get_spdy_conversation_data(packet_info *pinfo) |
| +{ |
| + conversation_t *conversation; |
|
Mike Belshe
2010/02/22 21:55:08
nit: conversation_t* conversation.
there are mor
Eric Shienbrood
2010/02/22 23:30:32
I'm not sure we should be following the Google sty
cbentzel
2010/02/23 02:21:58
Agree. I think the ultimate goal is to get this in
|
| + spdy_conv_t *conv_data; |
| + int retcode; |
| + |
| + conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0); |
|
Mike Belshe
2010/02/22 21:55:08
nit: 80 cols.
|
| + if (spdy_debug) { |
| + printf("\n===========================================\n\n"); |
| + printf("Conversation for frame #%d is %p\n", pinfo->fd->num, conversation); |
| + if (conversation) |
| + printf(" conv_data=%p\n", conversation_get_proto_data(conversation, proto_spdy)); |
| + } |
| + |
| + if(!conversation) /* Conversation does not exist yet - create it */ |
| + conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0); |
|
Mike Belshe
2010/02/22 21:55:08
nit: 80cols
|
| + |
| + /* Retrieve information from conversation |
| + */ |
| + conv_data = conversation_get_proto_data(conversation, proto_spdy); |
| + if(!conv_data) { |
| + /* Setup the conversation structure itself */ |
| + conv_data = se_alloc0(sizeof(spdy_conv_t)); |
| + |
| + conv_data->streams = NULL; |
| + if (spdy_decompress_headers) { |
| + conv_data->rqst_decompressor = se_alloc0(sizeof(z_stream)); |
| + conv_data->rply_decompressor = se_alloc0(sizeof(z_stream)); |
| + retcode = inflateInit(conv_data->rqst_decompressor); |
| + if (retcode == Z_OK) |
| + retcode = inflateInit(conv_data->rply_decompressor); |
| + if (retcode != Z_OK) |
| + printf("frame #%d: inflateInit() failed: %d\n", pinfo->fd->num, retcode); |
| + else if (spdy_debug) |
| + printf("created decompressor\n"); |
| + conv_data->dictionary_id = adler32(0L, Z_NULL, 0); |
| + conv_data->dictionary_id = adler32(conv_data->dictionary_id, |
| + spdy_dictionary, |
| + sizeof(spdy_dictionary)); |
| + } |
| + |
| + conversation_add_proto_data(conversation, proto_spdy, conv_data); |
| + register_postseq_cleanup_routine(reset_decompressors); |
| + } |
| + return conv_data; |
| +} |
| + |
| +static void |
| +spdy_save_stream_info(spdy_conv_t *conv_data, |
| + guint32 stream_id, |
| + gchar *content_type, |
| + gchar *content_type_params, |
| + gchar *content_encoding) |
| +{ |
| + spdy_stream_info_t *si; |
| + |
| + if (conv_data->streams == NULL) |
| + conv_data->streams = g_array_new(FALSE, TRUE, sizeof(spdy_stream_info_t *)); |
| + if (stream_id < conv_data->streams->len) |
| + DISSECTOR_ASSERT(g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) == NULL); |
| + else |
| + g_array_set_size(conv_data->streams, stream_id+1); |
| + si = se_alloc(sizeof(spdy_stream_info_t)); |
| + si->content_type = content_type; |
| + si->content_type_parameters = content_type_params; |
| + si->content_encoding = content_encoding; |
| + si->data_frames = NULL; |
| + si->num_data_frames = 0; |
| + si->assembled_data = NULL; |
| + g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) = si; |
| + if (spdy_debug) |
| + printf("Saved stream info for ID %u, content type %s\n", stream_id, content_type); |
| +} |
| + |
| +static spdy_stream_info_t * |
| +spdy_get_stream_info(spdy_conv_t *conv_data, guint32 stream_id) |
| +{ |
| + if (conv_data->streams == NULL || stream_id >= conv_data->streams->len) |
| + return NULL; |
| + else |
| + return g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id); |
| +} |
| + |
| +static void |
| +spdy_add_data_chunk(spdy_conv_t *conv_data, guint32 stream_id, guint32 frame, |
| + guint8 *data, guint32 length) |
| +{ |
| + spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id); |
| + |
| + if (si == NULL) { |
| + if (spdy_debug) printf("No stream_info found for stream %d\n", stream_id); |
| + } else { |
| + spdy_data_frame_t *df = g_malloc(sizeof(spdy_data_frame_t)); |
| + df->data = data; |
| + df->length = length; |
| + df->framenum = frame; |
| + si->data_frames = g_slist_append(si->data_frames, df); |
| + ++si->num_data_frames; |
| + if (spdy_debug) |
| + printf("Saved %u bytes of data for stream %u frame %u\n", |
| + length, stream_id, df->framenum); |
| + } |
| +} |
| + |
| +static void |
| +spdy_increment_data_chunk_count(spdy_conv_t *conv_data, guint32 stream_id) |
| +{ |
| + spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id); |
| + if (si != NULL) |
| + ++si->num_data_frames; |
| +} |
| + |
| +/* |
| + * Return the number of data frames saved so far for the specified stream. |
| + */ |
| +static guint |
| +spdy_get_num_data_frames(spdy_conv_t *conv_data, guint32 stream_id) |
| +{ |
| + spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id); |
| + |
| + return si == NULL ? 0 : si->num_data_frames; |
| +} |
| + |
| +static spdy_stream_info_t * |
| +spdy_assemble_data_frames(spdy_conv_t *conv_data, guint32 stream_id) |
| +{ |
| + spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id); |
| + tvbuff_t *tvb; |
| + |
| + if (si == NULL) |
| + return NULL; |
| + |
| + /* |
| + * Compute the total amount of data and concatenate the |
| + * data chunks, if it hasn't already been done. |
| + */ |
| + if (si->assembled_data == NULL) { |
| + spdy_data_frame_t *df; |
| + guint8 *data; |
| + guint32 datalen; |
| + guint32 offset; |
| + guint32 framenum; |
| + GSList *dflist = si->data_frames; |
| + if (dflist == NULL) |
| + return si; |
| + dflist = si->data_frames; |
| + datalen = 0; |
| + /* |
| + * I'd like to use a composite tvbuff here, but since |
| + * only a real-data tvbuff can be the child of another |
| + * tvb, I can't. It would be nice if this limitation |
| + * could be fixed. |
| + */ |
| + while (dflist != NULL) { |
| + df = dflist->data; |
| + datalen += df->length; |
| + dflist = g_slist_next(dflist); |
| + } |
| + if (datalen != 0) { |
| + data = se_alloc(datalen); |
| + dflist = si->data_frames; |
| + offset = 0; |
| + framenum = 0; |
| + while (dflist != NULL) { |
| + df = dflist->data; |
| + memcpy(data+offset, df->data, df->length); |
| + offset += df->length; |
| + dflist = g_slist_next(dflist); |
| + } |
| + tvb = tvb_new_real_data(data, datalen, datalen); |
| + si->assembled_data = tvb; |
| + } |
| + } |
| + return si; |
| +} |
| + |
| +static void |
| +spdy_discard_data_frames(spdy_stream_info_t *si) |
| +{ |
| + GSList *dflist = si->data_frames; |
| + spdy_data_frame_t *df; |
| + |
| + if (dflist == NULL) |
| + return; |
| + while (dflist != NULL) { |
| + df = dflist->data; |
| + if (df->data != NULL) { |
| + g_free(df->data); |
| + df->data = NULL; |
| + } |
| + dflist = g_slist_next(dflist); |
| + } |
| + /*g_slist_free(si->data_frames); |
| + si->data_frames = NULL; */ |
| +} |
| + |
| +static int |
| +dissect_spdy_data_frame(tvbuff_t *tvb, int offset, |
| + packet_info *pinfo, |
| + proto_tree *top_level_tree, |
| + proto_tree *spdy_tree, |
| + proto_item *spdy_proto, |
| + spdy_conv_t *conv_data) |
| +{ |
| + guint32 stream_id; |
| + guint8 flags; |
| + guint32 frame_length; |
| + proto_item *ti; |
| + proto_tree *flags_tree; |
| + guint32 reported_datalen; |
| + guint32 datalen; |
| + dissector_table_t media_type_subdissector_table; |
| + dissector_table_t port_subdissector_table; |
| + dissector_handle_t handle; |
| + guint num_data_frames; |
| + gboolean dissected; |
| + |
| + stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE); |
| + flags = tvb_get_guint8(tvb, offset+4); |
| + frame_length = tvb_get_ntoh24(tvb, offset+5); |
| + |
| + if (spdy_debug) |
| + printf("Data frame [stream_id=%u flags=0x%x length=%d]\n", |
| + stream_id, flags, frame_length); |
| + if (spdy_tree) proto_item_append_text(spdy_tree, ", data frame"); |
| + col_add_fstr(pinfo->cinfo, COL_INFO, "DATA[%u] length=%d", |
| + stream_id, frame_length); |
| + |
| + proto_item_append_text(spdy_proto, ":%s stream=%d length=%d", |
| + flags & SPDY_FIN ? " [FIN]" : "", |
| + stream_id, frame_length); |
| + |
| + proto_tree_add_boolean(spdy_tree, hf_spdy_control_bit, tvb, offset, 1, 0); |
| + proto_tree_add_uint(spdy_tree, hf_spdy_streamid, tvb, offset, 4, stream_id); |
| + ti = proto_tree_add_uint_format(spdy_tree, hf_spdy_flags, tvb, offset+4, 1, flags, |
| + "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : ""); |
| + |
| + flags_tree = proto_item_add_subtree(ti, ett_spdy_flags); |
| + proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, offset+4, 1, flags); |
| + proto_tree_add_uint(spdy_tree, hf_spdy_length, tvb, offset+5, 3, frame_length); |
| + |
| + datalen = tvb_length_remaining(tvb, offset); |
| + if (datalen > frame_length) |
| + datalen = frame_length; |
| + |
| + reported_datalen = tvb_reported_length_remaining(tvb, offset); |
| + if (reported_datalen > frame_length) |
| + reported_datalen = frame_length; |
| + |
| + num_data_frames = spdy_get_num_data_frames(conv_data, stream_id); |
| + if (datalen != 0 || num_data_frames != 0) { |
| + /* |
| + * There's stuff left over; process it. |
| + */ |
| + tvbuff_t *next_tvb = NULL; |
| + tvbuff_t *data_tvb = NULL; |
| + spdy_stream_info_t *si = NULL; |
| + void *save_private_data = NULL; |
| + guint8 *copied_data; |
| + gboolean private_data_changed = FALSE; |
| + gboolean is_single_chunk = FALSE; |
| + gboolean have_entire_body; |
| + |
| + /* |
| + * Create a tvbuff for the payload. |
| + */ |
| + if (datalen != 0) { |
| + next_tvb = tvb_new_subset(tvb, offset+8, datalen, |
| + reported_datalen); |
| + is_single_chunk = num_data_frames == 0 && (flags & SPDY_FIN) != 0; |
| + if (!pinfo->fd->flags.visited) { |
| + if (!is_single_chunk) { |
| + if (spdy_assemble_entity_bodies) { |
| + copied_data = tvb_memdup(next_tvb, 0, datalen); |
| + spdy_add_data_chunk(conv_data, stream_id, pinfo->fd->num, |
| + copied_data, datalen); |
| + } else |
| + spdy_increment_data_chunk_count(conv_data, stream_id); |
| + } |
| + } |
| + } else |
| + is_single_chunk = (num_data_frames == 1); |
| + |
| + if (!(flags & SPDY_FIN)) { |
| + col_set_fence(pinfo->cinfo, COL_INFO); |
| + col_add_fstr(pinfo->cinfo, COL_INFO, " (partial entity)"); |
| + proto_item_append_text(spdy_proto, " (partial entity body)"); |
| + /* would like the proto item to say */ |
| + /* " (entity body fragment N of M)" */ |
| + goto body_dissected; |
| + } |
| + have_entire_body = is_single_chunk; |
| + /* |
| + * On seeing the last data frame in a stream, we can |
| + * reassemble the frames into one data block. |
| + */ |
| + si = spdy_assemble_data_frames(conv_data, stream_id); |
| + if (si == NULL) |
| + goto body_dissected; |
| + data_tvb = si->assembled_data; |
| + if (spdy_assemble_entity_bodies) |
| + have_entire_body = TRUE; |
| + |
| + if (!have_entire_body) |
| + goto body_dissected; |
| + |
| + if (data_tvb == NULL) |
| + data_tvb = next_tvb; |
| + else |
| + add_new_data_source(pinfo, data_tvb, "Assembled entity body"); |
| + |
| + if (have_entire_body && si->content_encoding != NULL && |
| + g_ascii_strcasecmp(si->content_encoding, "identity") != 0) { |
| + /* |
| + * We currently can't handle, for example, "compress"; |
| + * just handle them as data for now. |
| + * |
| + * After July 7, 2004 the LZW patent expires, so support |
| + * might be added then. However, I don't think that |
| + * anybody ever really implemented "compress", due to |
| + * the aforementioned patent. |
| + */ |
| + tvbuff_t *uncomp_tvb = NULL; |
| + proto_item *e_ti = NULL; |
| + proto_item *ce_ti = NULL; |
| + proto_tree *e_tree = NULL; |
| + |
| + if (spdy_decompress_body && |
| + (g_ascii_strcasecmp(si->content_encoding, "gzip") == 0 || |
| + g_ascii_strcasecmp(si->content_encoding, "deflate") |
| + == 0)) { |
| + |
| + uncomp_tvb = tvb_child_uncompress(tvb, data_tvb, 0, |
| + tvb_length(data_tvb)); |
| + } |
| + /* |
| + * Add the encoded entity to the protocol tree |
| + */ |
| + e_ti = proto_tree_add_text(top_level_tree, data_tvb, |
| + 0, tvb_length(data_tvb), |
| + "Content-encoded entity body (%s): %u bytes", |
| + si->content_encoding, |
| + tvb_length(data_tvb)); |
| + e_tree = proto_item_add_subtree(e_ti, ett_spdy_encoded_entity); |
| + if (si->num_data_frames > 1) { |
| + GSList *dflist; |
| + spdy_data_frame_t *df; |
| + guint32 framenum; |
| + ce_ti = proto_tree_add_text(e_tree, data_tvb, 0, |
| + tvb_length(data_tvb), |
| + "Assembled from %d frames in packet(s)", si->num_data_frames); |
| + dflist = si->data_frames; |
| + framenum = 0; |
| + while (dflist != NULL) { |
| + df = dflist->data; |
| + if (framenum != df->framenum) { |
| + proto_item_append_text(ce_ti, " #%u", df->framenum); |
| + framenum = df->framenum; |
| + } |
| + dflist = g_slist_next(dflist); |
| + } |
| + } |
| + |
| + if (uncomp_tvb != NULL) { |
| + /* |
| + * Decompression worked |
| + */ |
| + |
| + /* XXX - Don't free this, since it's possible |
| + * that the data was only partially |
| + * decompressed, such as when desegmentation |
| + * isn't enabled. |
| + * |
| + tvb_free(next_tvb); |
| + */ |
| + proto_item_append_text(e_ti, " -> %u bytes", tvb_length(uncomp_tvb)); |
| + data_tvb = uncomp_tvb; |
| + add_new_data_source(pinfo, data_tvb, "Uncompressed entity body"); |
| + } else { |
| + if (spdy_decompress_body) |
| + proto_item_append_text(e_ti, " [Error: Decompression failed]"); |
| + call_dissector(data_handle, data_tvb, pinfo, e_tree); |
| + |
| + goto body_dissected; |
| + } |
| + } |
| + if (si != NULL) |
| + spdy_discard_data_frames(si); |
| + /* |
| + * Do subdissector checks. |
| + * |
| + * First, check whether some subdissector asked that they |
| + * be called if something was on some particular port. |
| + */ |
| + |
| + port_subdissector_table = find_dissector_table("http.port"); |
| + media_type_subdissector_table = find_dissector_table("media_type"); |
| + if (have_entire_body && port_subdissector_table != NULL) |
| + handle = dissector_get_port_handle(port_subdissector_table, |
| + pinfo->match_port); |
| + else |
| + handle = NULL; |
| + if (handle == NULL && have_entire_body && si->content_type != NULL && |
| + media_type_subdissector_table != NULL) { |
| + /* |
| + * We didn't find any subdissector that |
| + * registered for the port, and we have a |
| + * Content-Type value. Is there any subdissector |
| + * for that content type? |
| + */ |
| + save_private_data = pinfo->private_data; |
| + private_data_changed = TRUE; |
| + |
| + if (si->content_type_parameters) |
| + pinfo->private_data = ep_strdup(si->content_type_parameters); |
| + else |
| + pinfo->private_data = NULL; |
| + /* |
| + * Calling the string handle for the media type |
| + * dissector table will set pinfo->match_string |
| + * to si->content_type for us. |
| + */ |
| + pinfo->match_string = si->content_type; |
| + handle = dissector_get_string_handle( |
| + media_type_subdissector_table, |
| + si->content_type); |
| + } |
| + if (handle != NULL) { |
| + /* |
| + * We have a subdissector - call it. |
| + */ |
| + dissected = call_dissector(handle, data_tvb, pinfo, top_level_tree); |
| + } else |
| + dissected = FALSE; |
| + |
| + if (dissected) { |
| + /* |
| + * The subdissector dissected the body. |
| + * Fix up the top-level item so that it doesn't |
| + * include the stuff for that protocol. |
| + */ |
| + if (ti != NULL) |
| + proto_item_set_len(ti, offset); |
| + } else if (have_entire_body && si->content_type != NULL) { |
| + /* |
| + * Calling the default media handle if there is a content-type that |
| + * wasn't handled above. |
| + */ |
| + call_dissector(media_handle, next_tvb, pinfo, top_level_tree); |
| + } else { |
| + /* Call the default data dissector */ |
| + call_dissector(data_handle, next_tvb, pinfo, top_level_tree); |
| + } |
| + |
| +body_dissected: |
| + /* |
| + * Do *not* attempt at freeing the private data; |
| + * it may be in use by subdissectors. |
| + */ |
| + if (private_data_changed) /*restore even NULL value*/ |
| + pinfo->private_data = save_private_data; |
| + /* |
| + * We've processed "datalen" bytes worth of data |
| + * (which may be no data at all); advance the |
| + * offset past whatever data we've processed. |
| + */ |
| + } |
| + return frame_length + 8; |
| +} |
| + |
| +static guint8 * |
| +spdy_decompress_header_block(tvbuff_t *tvb, z_streamp decomp, |
| + guint32 dictionary_id, int offset, |
| + guint32 length, guint *uncomp_length) |
| +{ |
| + int retcode; |
| + size_t bufsize = 16384; |
| + const guint8 *hptr = tvb_get_ptr(tvb, offset, length); |
| + guint8 *uncomp_block = ep_alloc(bufsize); |
| + decomp->next_in = (Bytef *)hptr; |
| + decomp->avail_in = length; |
| + decomp->next_out = uncomp_block; |
| + decomp->avail_out = bufsize; |
| + retcode = inflate(decomp, Z_SYNC_FLUSH); |
| + if (retcode == Z_NEED_DICT) { |
| + if (decomp->adler != dictionary_id) { |
| + printf("decompressor wants dictionary %#x, but we have %#x\n", |
| + (guint)decomp->adler, dictionary_id); |
| + } else { |
| + retcode = inflateSetDictionary(decomp, |
| + spdy_dictionary, |
| + sizeof(spdy_dictionary)); |
|
Mike Belshe
2010/02/22 21:55:08
This should be sizeof(spdy_dictionary)-1. Probabl
Eric Shienbrood
2010/02/22 23:30:32
Has this changed? It should have been sizeof(...)-
|
| + if (retcode == Z_OK) |
| + retcode = inflate(decomp, Z_SYNC_FLUSH); |
| + } |
| + } |
| + |
| + if (retcode != Z_OK) { |
| + return NULL; |
| + } else { |
| + *uncomp_length = bufsize - decomp->avail_out; |
| + if (spdy_debug) |
| + printf("Inflation SUCCEEDED. uncompressed size=%d\n", *uncomp_length); |
| + if (decomp->avail_in != 0) |
| + if (spdy_debug) |
| + printf(" but there were %d input bytes left over\n", decomp->avail_in); |
| + } |
| + return se_memdup(uncomp_block, *uncomp_length); |
| +} |
| + |
| +/* |
| + * Try to determine heuristically whether the header block is |
| + * compressed. For an uncompressed block, the first two bytes |
| + * gives the number of headers. Each header name and value is |
| + * a two-byte length followed by ASCII characters. |
| + */ |
| +static gboolean |
| +spdy_check_header_compression(tvbuff_t *tvb, |
| + int offset, |
| + guint32 frame_length) |
| +{ |
| + guint16 length; |
| + if (!tvb_bytes_exist(tvb, offset, 6)) |
| + return 1; |
| + length = tvb_get_ntohs(tvb, offset); |
| + if (length > frame_length) |
| + return 1; |
| + length = tvb_get_ntohs(tvb, offset+2); |
| + if (length > frame_length) |
| + return 1; |
| + if (spdy_debug) printf("Looks like the header block is not compressed\n"); |
| + return 0; |
| +} |
| + |
| +static spdy_frame_info_t * |
| +spdy_save_header_block(frame_data *fd, |
| + guint32 stream_id, |
| + guint frame_type, |
| + guint8 *header, |
| + guint length) |
| +{ |
| + GSList *filist = p_get_proto_data(fd, proto_spdy); |
| + spdy_frame_info_t *frame_info = se_alloc(sizeof(spdy_frame_info_t)); |
| + if (filist != NULL) |
| + p_remove_proto_data(fd, proto_spdy); |
| + frame_info->stream_id = stream_id; |
| + frame_info->header_block = header; |
| + frame_info->header_block_len = length; |
| + frame_info->frame_type = frame_type; |
| + filist = g_slist_append(filist, frame_info); |
| + p_add_proto_data(fd, proto_spdy, filist); |
| + return frame_info; |
| + /* TODO(ers) these need to get deleted when no longer needed */ |
| +} |
| + |
| +static spdy_frame_info_t * |
| +spdy_find_saved_header_block(frame_data *fd, |
| + guint32 stream_id, |
| + guint16 frame_type) |
| +{ |
| + GSList *filist = p_get_proto_data(fd, proto_spdy); |
| + while (filist != NULL) { |
| + spdy_frame_info_t *fi = filist->data; |
| + if (fi->stream_id == stream_id && fi->frame_type == frame_type) |
| + return fi; |
| + filist = g_slist_next(filist); |
| + } |
| + return NULL; |
| +} |
| + |
| +/* |
| + * Given a content type string that may contain optional parameters, |
| + * return the parameter string, if any, otherwise return NULL. This |
| + * also has the side effect of null terminating the content type |
| + * part of the original string. |
| + */ |
| +static gchar * |
| +spdy_parse_content_type(gchar *content_type) |
| +{ |
| + gchar *cp = content_type; |
| + |
| + while (*cp != '\0' && *cp != ';' && !isspace(*cp)) { |
| + *cp = tolower(*cp); |
| + ++cp; |
| + } |
| + if (*cp == '\0') |
| + cp = NULL; |
| + |
| + if (cp != NULL) { |
| + *cp++ = '\0'; |
| + while (*cp == ';' || isspace(*cp)) |
| + ++cp; |
| + if (*cp != '\0') |
| + return cp; |
| + } |
| + return NULL; |
| +} |
| + |
| +static int |
| +dissect_spdy_message(tvbuff_t *tvb, int offset, packet_info *pinfo, |
| + proto_tree *tree, spdy_conv_t *conv_data) |
| +{ |
| + guint8 control_bit; |
| + guint16 version; |
| + guint16 frame_type; |
| + guint8 flags; |
| + guint32 frame_length; |
| + guint32 stream_id; |
| + gint priority; |
| + guint16 num_headers; |
| + guint32 fin_status; |
| + guint8 *frame_header; |
| + const char *proto_tag; |
| + const char *frame_type_name; |
| + proto_tree *spdy_tree = NULL; |
| + proto_item *ti = NULL; |
| + proto_item *spdy_proto = NULL; |
| + int orig_offset; |
| + int hoffset; |
| + int hdr_offset = 0; |
| + spdy_frame_type_t spdy_type; |
| + proto_tree *sub_tree; |
| + proto_tree *flags_tree; |
| + tvbuff_t *header_tvb = NULL; |
| + gboolean headers_compressed; |
| + gchar *hdr_verb = NULL; |
| + gchar *hdr_url = NULL; |
| + gchar *hdr_version = NULL; |
| + gchar *content_type = NULL; |
| + gchar *content_encoding = NULL; |
| + |
| + /* |
| + * Minimum size for a SPDY frame is 8 bytes. |
| + */ |
| + if (tvb_reported_length_remaining(tvb, offset) < 8) |
| + return -1; |
| + |
| + proto_tag = "SPDY"; |
| + |
| + if (check_col(pinfo->cinfo, COL_PROTOCOL)) |
| + col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_tag); |
| + |
| + /* |
| + * Is this a control frame or a data frame? |
| + */ |
| + orig_offset = offset; |
| + control_bit = tvb_get_bits8(tvb, offset << 3, 1); |
| + if (control_bit) { |
| + version = tvb_get_bits16(tvb, (offset << 3) + 1, 15, FALSE); |
| + frame_type = tvb_get_ntohs(tvb, offset+2); |
| + if (frame_type >= SPDY_INVALID) { |
| + return -1; |
| + } |
| + frame_header = ep_tvb_memdup(tvb, offset, 16); |
| + } else { |
| + version = 1; /* avoid gcc warning */ |
| + frame_type = SPDY_DATA; |
| + frame_header = NULL; /* avoid gcc warning */ |
| + } |
| + frame_type_name = frame_type_names[frame_type]; |
| + offset += 4; |
| + flags = tvb_get_guint8(tvb, offset); |
| + frame_length = tvb_get_ntoh24(tvb, offset+1); |
| + offset += 4; |
| + /* |
| + * Make sure there's as much data as the frame header says there is. |
| + */ |
| + if ((guint)tvb_reported_length_remaining(tvb, offset) < frame_length) { |
| + if (spdy_debug) |
| + printf("Not enough header data: %d vs. %d\n", |
| + frame_length, tvb_reported_length_remaining(tvb, offset)); |
| + return -1; |
| + } |
| + if (tree) { |
| + spdy_proto = proto_tree_add_item(tree, proto_spdy, tvb, orig_offset, frame_length+8, FALSE); |
| + spdy_tree = proto_item_add_subtree(spdy_proto, ett_spdy); |
| + } |
| + |
| + if (control_bit) { |
| + if (spdy_debug) |
| + printf("Control frame [version=%d type=%d flags=0x%x length=%d]\n", |
| + version, frame_type, flags, frame_length); |
| + if (tree) proto_item_append_text(spdy_tree, ", control frame"); |
| + } else { |
| + return dissect_spdy_data_frame(tvb, orig_offset, pinfo, tree, |
| + spdy_tree, spdy_proto, conv_data); |
| + } |
| + num_headers = 0; |
| + sub_tree = NULL; /* avoid gcc warning */ |
| + switch (frame_type) { |
| + case SPDY_SYN_STREAM: |
| + case SPDY_SYN_REPLY: |
| + if (tree) { |
| + int hf; |
| + hf = frame_type == SPDY_SYN_STREAM ? hf_spdy_syn_stream : hf_spdy_syn_reply; |
| + ti = proto_tree_add_bytes(spdy_tree, hf, tvb, |
| + orig_offset, 16, frame_header); |
| + sub_tree = proto_item_add_subtree(ti, ett_spdy_syn_stream); |
| + } |
| + stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE); |
| + offset += 4; |
| + priority = tvb_get_bits8(tvb, offset << 3, 2); |
| + offset += 2; |
| + if (tree) { |
| + proto_tree_add_boolean(sub_tree, hf_spdy_control_bit, tvb, orig_offset, 1, control_bit); |
| + proto_tree_add_uint(sub_tree, hf_spdy_version, tvb, orig_offset, 2, version); |
| + proto_tree_add_uint(sub_tree, hf_spdy_type, tvb, orig_offset+2, 2, frame_type); |
| + ti = proto_tree_add_uint_format(sub_tree, hf_spdy_flags, tvb, orig_offset+4, 1, flags, |
| + "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : ""); |
| + flags_tree = proto_item_add_subtree(ti, ett_spdy_flags); |
| + proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, orig_offset+4, 1, flags); |
| + proto_tree_add_uint(sub_tree, hf_spdy_length, tvb, orig_offset+5, 3, frame_length); |
| + proto_tree_add_uint(sub_tree, hf_spdy_streamid, tvb, orig_offset+8, 4, stream_id); |
| + proto_tree_add_uint(sub_tree, hf_spdy_priority, tvb, orig_offset+12, 1, priority); |
| + proto_item_append_text(spdy_proto, ": %s%s stream=%d length=%d", |
| + frame_type_name, |
| + flags & SPDY_FIN ? " [FIN]" : "", |
| + stream_id, frame_length); |
| + if (spdy_debug) |
| + printf(" stream ID=%u priority=%d\n", stream_id, priority); |
| + } |
| + break; |
| + |
| + case SPDY_FIN_STREAM: |
| + stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE); |
| + fin_status = tvb_get_ntohl(tvb, offset); |
| + // TODO(ers) fill in tree and summary |
| + offset += 8; |
| + break; |
| + |
| + case SPDY_HELLO: |
| + // TODO(ers) fill in tree and summary |
| + stream_id = 0; /* avoid gcc warning */ |
| + break; |
| + |
| + default: |
| + stream_id = 0; /* avoid gcc warning */ |
| + return -1; |
| + break; |
| + } |
| + |
| + /* |
| + * Process the name-value pairs one at a time, after possibly |
| + * decompressing the header block. |
| + */ |
| + if (frame_type == SPDY_SYN_STREAM || frame_type == SPDY_SYN_REPLY) { |
| + headers_compressed = spdy_check_header_compression(tvb, offset, frame_length); |
| + if (!spdy_decompress_headers || !headers_compressed) { |
| + header_tvb = tvb; |
| + hdr_offset = offset; |
| + } else { |
| + spdy_frame_info_t *per_frame_info = |
| + spdy_find_saved_header_block(pinfo->fd, |
| + stream_id, |
| + frame_type == SPDY_SYN_REPLY); |
| + if (per_frame_info == NULL) { |
| + guint uncomp_length; |
| + z_streamp decomp = frame_type == SPDY_SYN_STREAM ? |
| + conv_data->rqst_decompressor : conv_data->rply_decompressor; |
| + guint8 *uncomp_ptr = |
| + spdy_decompress_header_block(tvb, decomp, |
| + conv_data->dictionary_id, |
| + offset, frame_length-6, &uncomp_length); |
| + if (uncomp_ptr == NULL) { /* decompression failed */ |
| + if (spdy_debug) |
| + printf("Frame #%d: Inflation failed\n", pinfo->fd->num); |
| + proto_item_append_text(spdy_proto, " [Error: Header decompression failed]"); |
| + } else { |
| + if (spdy_debug) |
| + printf("Saving %u bytes of uncomp hdr\n", uncomp_length); |
| + per_frame_info = |
| + spdy_save_header_block(pinfo->fd, stream_id, frame_type == SPDY_SYN_REPLY, |
| + uncomp_ptr, uncomp_length); |
| + } |
| + } else if (spdy_debug) { |
| + printf("Found uncompressed header block len %u for stream %u frame_type=%d\n", |
| + per_frame_info->header_block_len, |
| + per_frame_info->stream_id, |
| + per_frame_info->frame_type); |
| + } |
| + if (per_frame_info != NULL) { |
| + header_tvb = tvb_new_child_real_data(tvb, |
| + per_frame_info->header_block, |
| + per_frame_info->header_block_len, |
| + per_frame_info->header_block_len); |
| + add_new_data_source(pinfo, header_tvb, "Uncompressed headers"); |
| + hdr_offset = 0; |
| + } |
| + } |
| + offset += frame_length-6; |
| + num_headers = tvb_get_ntohs(header_tvb, hdr_offset); |
| + hdr_offset += 2; |
| + if (header_tvb == NULL || |
| + (headers_compressed && !spdy_decompress_headers)) { |
| + num_headers = 0; |
| + ti = proto_tree_add_string(sub_tree, hf_spdy_num_headers_string, |
| + tvb, orig_offset+14, 2, |
| + "Unknown (header block is compressed)"); |
| + } else |
| + ti = proto_tree_add_uint(sub_tree, hf_spdy_num_headers, |
| + tvb, orig_offset+14, 2, num_headers); |
| + } |
| + spdy_type = SPDY_INVALID; /* type not known yet */ |
| + if (spdy_debug) |
| + printf(" %d Headers:\n", num_headers); |
| + if (num_headers > frame_length) { |
| + printf("Number of headers is greater than frame length!\n"); |
| + proto_item_append_text(ti, " [Error: Number of headers is larger than frame length]"); |
| + col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id); |
| + return frame_length+8; |
| + } |
| + hdr_verb = hdr_url = hdr_version = content_type = content_encoding = NULL; |
| + while (num_headers-- && tvb_reported_length_remaining(header_tvb, hdr_offset) != 0) { |
| + gchar *header_name; |
| + gchar *header_value; |
| + proto_tree *header_tree; |
| + proto_tree *name_tree; |
| + proto_tree *value_tree; |
| + proto_item *header; |
| + gint16 length; |
| + gint header_length = 0; |
| + |
| + hoffset = hdr_offset; |
| + |
| + header = proto_tree_add_item(spdy_tree, hf_spdy_header, header_tvb, |
| + hdr_offset, frame_length, FALSE); |
| + header_tree = proto_item_add_subtree(header, ett_spdy_header); |
| + |
| + length = tvb_get_ntohs(header_tvb, hdr_offset); |
| + hdr_offset += 2; |
| + header_name = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length); |
| + hdr_offset += length; |
| + header_length += hdr_offset - hoffset; |
| + if (tree) { |
| + ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Name: %s", |
| + header_name); |
| + name_tree = proto_item_add_subtree(ti, ett_spdy_header_name); |
| + proto_tree_add_uint(name_tree, hf_spdy_length, header_tvb, hoffset, 2, length); |
| + proto_tree_add_string_format(name_tree, hf_spdy_header_name_text, header_tvb, hoffset+2, length, |
| + header_name, "Text: %s", format_text(header_name, length)); |
| + } |
| + |
| + hoffset = hdr_offset; |
| + length = tvb_get_ntohs(header_tvb, hdr_offset); |
| + hdr_offset += 2; |
| + header_value = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length); |
| + hdr_offset += length; |
| + header_length += hdr_offset - hoffset; |
| + if (tree) { |
| + ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Value: %s", |
| + header_value); |
| + value_tree = proto_item_add_subtree(ti, ett_spdy_header_value); |
| + proto_tree_add_uint(value_tree, hf_spdy_length, header_tvb, hoffset, 2, length); |
| + proto_tree_add_string_format(value_tree, hf_spdy_header_value_text, header_tvb, hoffset+2, length, |
| + header_value, "Text: %s", format_text(header_value, length)); |
| + proto_item_append_text(header, ": %s: %s", header_name, header_value); |
| + proto_item_set_len(header, header_length); |
| + } |
| + if (spdy_debug) printf(" %s: %s\n", header_name, header_value); |
| + /* |
| + * TODO(ers) check that the header name contains only legal characters. |
| + */ |
| + if (g_ascii_strcasecmp(header_name, "method") == 0 || |
| + g_ascii_strcasecmp(header_name, "status") == 0) { |
| + hdr_verb = header_value; |
| + } else if (g_ascii_strcasecmp(header_name, "url") == 0) { |
| + hdr_url = header_value; |
| + } else if (g_ascii_strcasecmp(header_name, "version") == 0) { |
| + hdr_version = header_value; |
| + } else if (g_ascii_strcasecmp(header_name, "content-type") == 0) { |
| + content_type = se_strdup(header_value); |
| + } else if (g_ascii_strcasecmp(header_name, "content-encoding") == 0) { |
| + content_encoding = se_strdup(header_value); |
| + } |
| + } |
| + if (hdr_version != NULL) { |
| + if (hdr_url != NULL) { |
| + col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s %s", |
| + frame_type_name, stream_id, hdr_verb, hdr_url, hdr_version); |
| + } else { |
| + col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s", |
| + frame_type_name, stream_id, hdr_verb, hdr_version); |
| + } |
| + } else { |
| + col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id); |
| + } |
| + /* |
| + * If we expect data on this stream, we need to remember the content |
| + * type and content encoding. |
| + */ |
| + if (content_type != NULL && !pinfo->fd->flags.visited) { |
| + gchar *content_type_params = spdy_parse_content_type(content_type); |
| + spdy_save_stream_info(conv_data, stream_id, content_type, |
| + content_type_params, content_encoding); |
| + } |
| + |
| + return offset - orig_offset; |
| +} |
| + |
| +static int |
| +dissect_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) |
| +{ |
| + spdy_conv_t *conv_data; |
| + int offset = 0; |
| + int len; |
| + int firstpkt = 1; |
| + |
| + /* |
| + * The first byte of a SPDY packet must be either 0 or |
| + * 0x80. If it's not, assume that this is not SPDY. |
| + * (In theory, a data frame could have a stream ID |
| + * >= 2^24, in which case it won't have 0 for a first |
| + * byte, but this is a pretty reliable heuristic for |
| + * now.) |
| + */ |
| + guint8 first_byte = tvb_get_guint8(tvb, 0); |
| + if (first_byte != 0x80 && first_byte != 0x0) |
| + return 0; |
| + |
| + conv_data = get_spdy_conversation_data(pinfo); |
| + |
| + while (tvb_reported_length_remaining(tvb, offset) != 0) { |
| + if (!firstpkt) { |
| + col_add_fstr(pinfo->cinfo, COL_INFO, " >> "); |
| + col_set_fence(pinfo->cinfo, COL_INFO); |
| + } |
| + len = dissect_spdy_message(tvb, offset, pinfo, tree, conv_data); |
| + if (len <= 0) |
| + return 0; |
| + offset += len; |
| + /* |
| + * OK, we've set the Protocol and Info columns for the |
| + * first SPDY message; set a fence so that subsequent |
| + * SPDY messages don't overwrite the Info column. |
| + */ |
| + col_set_fence(pinfo->cinfo, COL_INFO); |
| + firstpkt = 0; |
| + } |
| + return 1; |
| +} |
| + |
| +static gboolean |
| +dissect_spdy_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) |
| +{ |
| + if (!value_is_in_range(global_spdy_tcp_range, pinfo->destport) && |
| + !value_is_in_range(global_spdy_tcp_range, pinfo->srcport)) |
| + return FALSE; |
| + return dissect_spdy(tvb, pinfo, tree) != 0; |
| +} |
| + |
| +static void reinit_spdy(void) |
| +{ |
| +} |
| + |
| +// NMAKE complains about flags_set_truth not being constant. Duplicate |
| +// the values inside of it. |
| +static const true_false_string tfs_spdy_set_notset = { "Set", "Not set" }; |
| + |
| +void |
| +proto_register_spdy(void) |
| +{ |
| + static hf_register_info hf[] = { |
| + { &hf_spdy_syn_stream, |
| + { "Syn Stream", "spdy.syn_stream", |
| + FT_BYTES, BASE_HEX, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_syn_reply, |
| + { "Syn Reply", "spdy.syn_reply", |
| + FT_BYTES, BASE_HEX, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_control_bit, |
| + { "Control bit", "spdy.control_bit", |
| + FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
| + "TRUE if SPDY control frame", HFILL }}, |
| + { &hf_spdy_version, |
| + { "Version", "spdy.version", |
| + FT_UINT16, BASE_DEC, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_type, |
| + { "Type", "spdy.type", |
| + FT_UINT16, BASE_DEC, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_flags, |
| + { "Flags", "spdy.flags", |
| + FT_UINT8, BASE_HEX, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_flags_fin, |
| + { "Fin", "spdy.flags.fin", |
| + FT_BOOLEAN, 8, TFS(&tfs_spdy_set_notset), |
| + SPDY_FIN, "", HFILL }}, |
| + { &hf_spdy_length, |
| + { "Length", "spdy.length", |
| + FT_UINT24, BASE_DEC, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_header, |
| + { "Header", "spdy.header", |
| + FT_NONE, BASE_NONE, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_header_name, |
| + { "Name", "spdy.header.name", |
| + FT_NONE, BASE_NONE, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_header_name_text, |
| + { "Text", "spdy.header.name.text", |
| + FT_STRING, BASE_NONE, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_header_value, |
| + { "Value", "spdy.header.value", |
| + FT_NONE, BASE_NONE, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_header_value_text, |
| + { "Text", "spdy.header.value.text", |
| + FT_STRING, BASE_NONE, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_streamid, |
| + { "Stream ID", "spdy.streamid", |
| + FT_UINT32, BASE_DEC, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_priority, |
| + { "Priority", "spdy.priority", |
| + FT_UINT8, BASE_DEC, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_num_headers, |
| + { "Number of headers", "spdy.numheaders", |
| + FT_UINT16, BASE_DEC, NULL, 0x0, |
| + "", HFILL }}, |
| + { &hf_spdy_num_headers_string, |
| + { "Number of headers", "spdy.numheaders", |
| + FT_STRING, BASE_NONE, NULL, 0x0, |
| + "", HFILL }}, |
| + }; |
| + static gint *ett[] = { |
| + &ett_spdy, |
| + &ett_spdy_syn_stream, |
| + &ett_spdy_syn_reply, |
| + &ett_spdy_fin_stream, |
| + &ett_spdy_flags, |
| + &ett_spdy_header, |
| + &ett_spdy_header_name, |
| + &ett_spdy_header_value, |
| + &ett_spdy_encoded_entity, |
| + }; |
| + |
| + module_t *spdy_module; |
| + |
| + proto_spdy = proto_register_protocol("SPDY", "SPDY", "spdy"); |
| + proto_register_field_array(proto_spdy, hf, array_length(hf)); |
| + proto_register_subtree_array(ett, array_length(ett)); |
| + new_register_dissector("spdy", dissect_spdy, proto_spdy); |
| + spdy_module = prefs_register_protocol(proto_spdy, reinit_spdy); |
| + prefs_register_bool_preference(spdy_module, "desegment_headers", |
| + "Reassemble SPDY control frames spanning multiple TCP segments", |
| + "Whether the SPDY dissector should reassemble control frames " |
| + "spanning multiple TCP segments. " |
| + "To use this option, you must also enable " |
| + "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", |
| + &spdy_desegment_control_frames); |
| + prefs_register_bool_preference(spdy_module, "desegment_body", |
| + "Reassemble SPDY bodies spanning multiple TCP segments", |
| + "Whether the SPDY dissector should reassemble " |
| + "data frames spanning multiple TCP segments. " |
| + "To use this option, you must also enable " |
| + "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", |
| + &spdy_desegment_data_frames); |
| + prefs_register_bool_preference(spdy_module, "assemble_data_frames", |
| + "Assemble SPDY bodies that consist of multiple DATA frames", |
| + "Whether the SPDY dissector should reassemble multiple " |
| + "data frames into an entity body.", |
| + &spdy_assemble_entity_bodies); |
| +#ifdef HAVE_LIBZ |
| + prefs_register_bool_preference(spdy_module, "decompress_headers", |
| + "Uncompress SPDY headers", |
| + "Whether to uncompress SPDY headers.", |
| + &spdy_decompress_headers); |
| + prefs_register_bool_preference(spdy_module, "decompress_body", |
| + "Uncompress entity bodies", |
| + "Whether to uncompress entity bodies that are compressed " |
| + "using \"Content-Encoding: \"", |
| + &spdy_decompress_body); |
| +#endif |
| + prefs_register_bool_preference(spdy_module, "debug_output", |
| + "Print debug info on stdout", |
| + "Print debug info on stdout", |
| + &spdy_debug); |
| +#if 0 |
| + prefs_register_string_preference(ssl_module, "debug_file", "SPDY debug file", |
| + "Redirect SPDY debug to file name; " |
| + "leave empty to disable debugging, " |
| + "or use \"" SPDY_DEBUG_USE_STDOUT "\"" |
| + " to redirect output to stdout\n", |
| + (const gchar **)&sdpy_debug_file_name); |
| +#endif |
| + prefs_register_obsolete_preference(spdy_module, "tcp_alternate_port"); |
| + |
| + range_convert_str(&global_spdy_tcp_range, TCP_DEFAULT_RANGE, 65535); |
| + spdy_tcp_range = range_empty(); |
| + prefs_register_range_preference(spdy_module, "tcp.port", "TCP Ports", |
| + "TCP Ports range", |
| + &global_spdy_tcp_range, 65535); |
| + |
| + range_convert_str(&global_spdy_ssl_range, SSL_DEFAULT_RANGE, 65535); |
| + spdy_ssl_range = range_empty(); |
| + prefs_register_range_preference(spdy_module, "ssl.port", "SSL/TLS Ports", |
| + "SSL/TLS Ports range", |
| + &global_spdy_ssl_range, 65535); |
| + |
| + spdy_handle = new_create_dissector_handle(dissect_spdy, proto_spdy); |
| + /* |
| + * Register for tapping |
| + */ |
| + spdy_tap = register_tap("spdy"); /* SPDY statistics tap */ |
| + spdy_eo_tap = register_tap("spdy_eo"); /* SPDY Export Object tap */ |
| +} |
| + |
| +void |
| +proto_reg_handoff_spdy(void) |
| +{ |
| + data_handle = find_dissector("data"); |
| + media_handle = find_dissector("media"); |
| + heur_dissector_add("tcp", dissect_spdy_heur, proto_spdy); |
| +} |
| + |
| +/* |
| + * Content-Type: message/http |
| + */ |
| + |
| +static gint proto_message_spdy = -1; |
| +static gint ett_message_spdy = -1; |
| + |
| +static void |
| +dissect_message_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) |
| +{ |
| + proto_tree *subtree; |
| + proto_item *ti; |
| + gint offset = 0, next_offset; |
| + gint len; |
| + |
| + if (check_col(pinfo->cinfo, COL_INFO)) |
| + col_append_str(pinfo->cinfo, COL_INFO, " (message/spdy)"); |
| + if (tree) { |
| + ti = proto_tree_add_item(tree, proto_message_spdy, |
| + tvb, 0, -1, FALSE); |
| + subtree = proto_item_add_subtree(ti, ett_message_spdy); |
| + while (tvb_reported_length_remaining(tvb, offset) != 0) { |
| + len = tvb_find_line_end(tvb, offset, |
| + tvb_ensure_length_remaining(tvb, offset), |
| + &next_offset, FALSE); |
| + if (len == -1) |
| + break; |
| + proto_tree_add_text(subtree, tvb, offset, next_offset - offset, |
| + "%s", tvb_format_text(tvb, offset, len)); |
| + offset = next_offset; |
| + } |
| + } |
| +} |
| + |
| +void |
| +proto_register_message_spdy(void) |
| +{ |
| + static gint *ett[] = { |
| + &ett_message_spdy, |
| + }; |
| + |
| + proto_message_spdy = proto_register_protocol( |
| + "Media Type: message/spdy", |
| + "message/spdy", |
| + "message-spdy" |
| + ); |
| + proto_register_subtree_array(ett, array_length(ett)); |
| +} |
| + |
| +void |
| +proto_reg_handoff_message_spdy(void) |
| +{ |
| + dissector_handle_t message_spdy_handle; |
| + |
| + message_spdy_handle = create_dissector_handle(dissect_message_spdy, |
| + proto_message_spdy); |
| + |
| + dissector_add_string("media_type", "message/spdy", message_spdy_handle); |
| + |
| + reinit_spdy(); |
| +} |