/* 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();
}