| /* 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 "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_associated_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 |
| |
| /* |
| * SSDP is implemented atop HTTP (yes, it really *does* run over UDP). |
| */ |
| #define TCP_PORT_SSDP 1900 |
| #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"); |
| } |
| |
| static spdy_conv_t * |
| get_spdy_conversation_data(packet_info *pinfo) |
| { |
| conversation_t *conversation; |
| spdy_conv_t *conv_data; |
| int retcode; |
| |
| conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0); |
| 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); |
| |
| /* 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; */ |
| } |
| |
| // TODO(cbentzel): tvb_child_uncompress should be exported by wireshark. |
| static tvbuff_t* spdy_tvb_child_uncompress(tvbuff_t *parent _U_, tvbuff_t *tvb, |
| int offset, int comprlen) |
| { |
| tvbuff_t *new_tvb = tvb_uncompress(tvb, offset, comprlen); |
| if (new_tvb) |
| tvb_set_child_real_data_tvbuff (parent, new_tvb); |
| return new_tvb; |
| } |
| |
| 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 = spdy_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)); |
| 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; |
| } |
| |
| // TODO(cbentzel): Change wireshark to export p_remove_proto_data, rather |
| // than duplicating code here. |
| typedef struct _spdy_frame_proto_data { |
| int proto; |
| void *proto_data; |
| } spdy_frame_proto_data; |
| |
| static gint spdy_p_compare(gconstpointer a, gconstpointer b) |
| { |
| const spdy_frame_proto_data *ap = (const spdy_frame_proto_data *)a; |
| const spdy_frame_proto_data *bp = (const spdy_frame_proto_data *)b; |
| |
| if (ap -> proto > bp -> proto) |
| return 1; |
| else if (ap -> proto == bp -> proto) |
| return 0; |
| else |
| return -1; |
| |
| } |
| |
| static void spdy_p_remove_proto_data(frame_data *fd, int proto) |
| { |
| spdy_frame_proto_data temp; |
| GSList *item; |
| |
| temp.proto = proto; |
| temp.proto_data = NULL; |
| |
| item = g_slist_find_custom(fd->pfd, (gpointer *)&temp, spdy_p_compare); |
| |
| if (item) { |
| fd->pfd = g_slist_remove(fd->pfd, item->data); |
| } |
| } |
| |
| 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) |
| spdy_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; |
| guint32 associated_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; |
| if (frame_type == SPDY_SYN_STREAM) { |
| associated_stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE); |
| offset += 4; |
| priority = tvb_get_bits8(tvb, offset << 3, 2); |
| offset += 2; |
| } else { |
| // The next two bytes have no meaning in SYN_REPLY |
| 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); |
| if (frame_type == SPDY_SYN_STREAM) { |
| proto_tree_add_uint(sub_tree, hf_spdy_associated_streamid, tvb, orig_offset+12, 4, associated_stream_id); |
| proto_tree_add_uint(sub_tree, hf_spdy_priority, tvb, orig_offset+16, 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 + 8 - (offset - orig_offset), |
| &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]"); |
| // Should we just bail here? |
| } 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 = orig_offset + 8 + frame_length; |
| 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, |
| frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset + 14, |
| 2, |
| "Unknown (header block is compressed)"); |
| } else |
| ti = proto_tree_add_uint(sub_tree, hf_spdy_num_headers, |
| tvb, |
| frame_type == SPDY_SYN_STREAM ? orig_offset+18 : 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_NONE, NULL, 0x0, |
| "", HFILL }}, |
| { &hf_spdy_syn_reply, |
| { "Syn Reply", "spdy.syn_reply", |
| FT_BYTES, BASE_NONE, 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_associated_streamid, |
| { "Associated Stream ID", "spdy.associated.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(); |
| } |