forked from osmocom/wireshark
868 lines
32 KiB
C
868 lines
32 KiB
C
/* packet-http2.c
|
|
* Routines for HTTP2 dissection
|
|
* Copyright 2013, Alexis La Goutte <alexis.lagoutte@gmail.com>
|
|
* Copyright 2013, Stephen Ludin <sludin@ludin.org>
|
|
*
|
|
* $Id$
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
/*
|
|
* The information used comes from:
|
|
* Hypertext Transfer Protocol version 2.0 draft-ietf-httpbis-http2-06
|
|
* HTTP Header Compression draft-ietf-httpbis-header-compression-02
|
|
*
|
|
* TODO
|
|
* Support HTTP Header Compression (draft-ietf-httpbis-header-compression)
|
|
* Enhance display of Data
|
|
* Reassembling of continuation frame (and other frame)
|
|
* Add same tap and ping/pong time response
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
|
|
#include <epan/packet.h>
|
|
#include <epan/prefs.h>
|
|
#include <epan/expert.h>
|
|
|
|
#include "packet-tcp.h"
|
|
|
|
/* Packet Header */
|
|
static int proto_http2 = -1;
|
|
static int hf_http2 = -1;
|
|
static int hf_http2_length = -1;
|
|
static int hf_http2_type = -1;
|
|
static int hf_http2_r = -1;
|
|
static int hf_http2_streamid = -1;
|
|
static int hf_http2_magic = -1;
|
|
static int hf_http2_unknown = -1;
|
|
/* Flags */
|
|
static int hf_http2_flags = -1;
|
|
static int hf_http2_flags_end_stream = -1;
|
|
static int hf_http2_flags_end_headers = -1;
|
|
static int hf_http2_flags_priority = -1;
|
|
static int hf_http2_flags_end_push_promise = -1;
|
|
static int hf_http2_flags_pong = -1;
|
|
static int hf_http2_flags_reserved = -1;
|
|
static int hf_http2_flags_reserved1 = -1;
|
|
static int hf_http2_flags_reserved2 = -1;
|
|
static int hf_http2_flags_reserved3 = -1;
|
|
static int hf_http2_flags_reserved4 = -1;
|
|
/* Headers */
|
|
static int hf_http2_headers_r = -1;
|
|
static int hf_http2_headers_priority = -1;
|
|
static int hf_http2_headers = -1;
|
|
/* Priority */
|
|
static int hf_http2_priority_r = -1;
|
|
static int hf_http2_priority = -1;
|
|
/* RST Stream */
|
|
static int hf_http2_rst_stream_error = -1;
|
|
/* Settings */
|
|
static int hf_http2_settings = -1;
|
|
static int hf_http2_settings_r = -1;
|
|
static int hf_http2_settings_identifier = -1;
|
|
static int hf_http2_settings_max_concurrent_streams = -1;
|
|
static int hf_http2_settings_initial_window_size = -1;
|
|
static int hf_http2_settings_flow_control_options = -1;
|
|
static int hf_http2_settings_unknown = -1;
|
|
/* Push Promise */
|
|
static int hf_http2_push_promise_r = -1;
|
|
static int hf_http2_push_promise_promised_stream_id = -1;
|
|
static int hf_http2_push_promise_header = -1;
|
|
/* Ping */
|
|
static int hf_http2_ping = -1;
|
|
static int hf_http2_pong = -1;
|
|
/* Priority */
|
|
static int hf_http2_goaway_r = -1;
|
|
static int hf_http2_goaway_last_stream_id = -1;
|
|
static int hf_http2_goaway_error = -1;
|
|
static int hf_http2_goaway_addata = -1;
|
|
/* Window Update */
|
|
static int hf_http2_window_update_r = -1;
|
|
static int hf_http2_window_update_window_size_increment = -1;
|
|
/* Continuation */
|
|
static int hf_http2_continuation_header = -1;
|
|
|
|
static gint ett_http2 = -1;
|
|
static gint ett_http2_header = -1;
|
|
static gint ett_http2_flags = -1;
|
|
static gint ett_http2_settings = -1;
|
|
|
|
static expert_field ei_http2_flags_epp_old = EI_INIT;
|
|
|
|
|
|
static dissector_handle_t data_handle;
|
|
|
|
#define FRAME_HEADER_LENGTH 8
|
|
#define MAGIC_FRAME_LENGTH 24
|
|
#define MASK_HTTP2_RESERVED 0x80000000
|
|
#define MASK_HTTP2_STREAMID 0X7FFFFFFF
|
|
#define MASK_HTTP2_PRIORITY 0X7FFFFFFF
|
|
|
|
/* Header Type Code */
|
|
#define HTTP2_DATA 0
|
|
#define HTTP2_HEADERS 1
|
|
#define HTTP2_PRIORITY 2
|
|
#define HTTP2_RST_STREAM 3
|
|
#define HTTP2_SETTINGS 4
|
|
#define HTTP2_PUSH_PROMISE 5
|
|
#define HTTP2_PING 6
|
|
#define HTTP2_GOAWAY 7
|
|
#define HTTP2_WINDOW_UPDATE 9
|
|
#define HTTP2_CONTINUATION 10
|
|
|
|
static const value_string http2_type_vals[] = {
|
|
{ HTTP2_DATA, "DATA" },
|
|
{ HTTP2_HEADERS, "HEADERS" },
|
|
{ HTTP2_PRIORITY, "PRIORITY" },
|
|
{ HTTP2_RST_STREAM, "RST_STREAM" },
|
|
{ HTTP2_SETTINGS, "SETTINGS" },
|
|
{ HTTP2_PUSH_PROMISE, "PUSH_PROMISE" },
|
|
{ HTTP2_PING, "PING" },
|
|
{ HTTP2_GOAWAY, "GOAWAY" },
|
|
{ HTTP2_WINDOW_UPDATE, "WINDOW_UPDATE" },
|
|
{ HTTP2_CONTINUATION, "CONTINUATION" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* Flags */
|
|
#define HTTP2_FLAGS_ES 0x01
|
|
#define HTTP2_FLAGS_EH 0x04
|
|
#define HTTP2_FLAGS_PR 0x08
|
|
#define HTTP2_FLAGS_EPP_OLD 0x01 /* from draft-04 */
|
|
#define HTTP2_FLAGS_EPP 0x04
|
|
#define HTTP2_FLAGS_PO 0x01
|
|
#define HTTP2_FLAGS_R 0xFF
|
|
#define HTTP2_FLAGS_R1 0xFE
|
|
#define HTTP2_FLAGS_R2 0xFA
|
|
#define HTTP2_FLAGS_R3 0xF2
|
|
#define HTTP2_FLAGS_R4 0xFB
|
|
|
|
/* Magic Header : PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n */
|
|
static guint8 kMagicHello[] = {
|
|
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
|
|
0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
|
|
0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
|
|
};
|
|
|
|
/* Error Codes */
|
|
#define EC_NO_ERROR 0
|
|
#define EC_PROTOCOL_ERROR 1
|
|
#define EC_INTERNAL_ERROR 2
|
|
#define EC_FLOW_CONTROL_ERROR 4
|
|
#define EC_STREAM_CLOSED 5
|
|
#define EC_FRAME_TOO_LARGE 6
|
|
#define EC_REFUSED_STREAM 7
|
|
#define EC_CANCEL 8
|
|
#define EC_COMPRESSION_ERROR 9
|
|
|
|
static const value_string http2_error_codes_vals[] = {
|
|
{ EC_NO_ERROR, "NO ERROR" },
|
|
{ EC_PROTOCOL_ERROR, "PROTOCOL_ERROR" },
|
|
{ EC_INTERNAL_ERROR, "INTERNAL_ERROR" },
|
|
{ EC_FLOW_CONTROL_ERROR, "FLOW_CONTROL_ERROR" },
|
|
{ EC_STREAM_CLOSED, "STREAM_CLOSED" },
|
|
{ EC_FRAME_TOO_LARGE, "FRAME_TOO_LARGE" },
|
|
{ EC_REFUSED_STREAM, "REFUSED_STREAM" },
|
|
{ EC_CANCEL, "CANCEL" },
|
|
{ EC_COMPRESSION_ERROR, "COMPRESSION_ERROR" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* Settings */
|
|
#define HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS 4
|
|
#define HTTP2_SETTINGS_INITIAL_WINDOW_SIZE 7
|
|
#define HTTP2_SETTINGS_FLOW_CONTROL_OPTIONS 10
|
|
|
|
static const value_string http2_settings_vals[] = {
|
|
{ HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, "Max concurrent streams" },
|
|
{ HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, "Initial Windows size" },
|
|
{ HTTP2_SETTINGS_FLOW_CONTROL_OPTIONS, "Flow Control Options" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* Flags
|
|
+--------+---------------+---------------------------+--------------+
|
|
| Frame | Name | Flags | Section |
|
|
| Type | | | |
|
|
+--------+---------------+---------------------------+--------------+
|
|
| 0 | DATA | END_STREAM(1) | Section 6.1 |
|
|
| 1 | HEADERS | END_STREAM(1), | Section 6.2 |
|
|
| | | END_HEADERS(4), | |
|
|
| | | PRIORITY(8) | |
|
|
| 2 | PRIORITY | - | Section 6.3 |
|
|
| 3 | RST_STREAM | - | Section 6.4 |
|
|
| 4 | SETTINGS | - | Section 6.5 |
|
|
| 5 | PUSH_PROMISE | END_PUSH_PROMISE(4) | Section 6.6 |
|
|
| 6 | PING | PONG(1) | Section 6.7 |
|
|
| 7 | GOAWAY | - | Section 6.8 |
|
|
| 9 | WINDOW_UPDATE | - | Section 6.9 |
|
|
| 10 | CONTINUATION | END_STREAM(1), | Section 6.10 |
|
|
| | | END_HEADERS(4) | |
|
|
+--------+---------------+---------------------------+--------------+
|
|
*/
|
|
static guint8
|
|
dissect_http2_header_flags(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree, guint offset, guint8 type)
|
|
{
|
|
proto_item *ti_flags;
|
|
proto_tree *flags_tree;
|
|
guint8 flags;
|
|
|
|
ti_flags = proto_tree_add_item(http2_tree, hf_http2_flags, tvb, offset, 1, ENC_NA);
|
|
flags_tree = proto_item_add_subtree(ti_flags, ett_http2_flags);
|
|
flags = tvb_get_guint8(tvb, offset);
|
|
|
|
switch(type){
|
|
case HTTP2_DATA:
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_end_stream, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_reserved1, tvb, offset, 1, ENC_NA);
|
|
break;
|
|
case HTTP2_HEADERS:
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_end_stream, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_end_headers, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_priority, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_reserved3, tvb, offset, 1, ENC_NA);
|
|
break;
|
|
case HTTP2_PUSH_PROMISE:
|
|
if(flags & HTTP2_FLAGS_EPP_OLD)
|
|
{
|
|
expert_add_info(pinfo, ti_flags, &ei_http2_flags_epp_old );
|
|
}
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_end_push_promise, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_reserved4, tvb, offset, 1, ENC_NA);
|
|
break;
|
|
case HTTP2_PING:
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_pong, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_reserved1, tvb, offset, 1, ENC_NA);
|
|
break;
|
|
case HTTP2_CONTINUATION:
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_end_stream, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_end_headers, tvb, offset, 1, ENC_NA);
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_reserved2, tvb, offset, 1, ENC_NA);
|
|
break;
|
|
case HTTP2_PRIORITY:
|
|
case HTTP2_RST_STREAM:
|
|
case HTTP2_SETTINGS:
|
|
case HTTP2_GOAWAY:
|
|
case HTTP2_WINDOW_UPDATE:
|
|
default:
|
|
/* No flags !*/
|
|
proto_tree_add_item(flags_tree, hf_http2_flags_reserved, tvb, offset, 1, ENC_NA);
|
|
break;
|
|
}
|
|
|
|
|
|
return flags;
|
|
}
|
|
|
|
/* Headers */
|
|
static int
|
|
dissect_http2_headers(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags)
|
|
{
|
|
|
|
if(flags & HTTP2_FLAGS_PR)
|
|
{
|
|
proto_tree_add_item(http2_tree, hf_http2_headers_r, tvb, offset, 4, ENC_NA);
|
|
proto_tree_add_item(http2_tree, hf_http2_headers_priority, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
}
|
|
/* TODO : Support header decompression */
|
|
proto_tree_add_item(http2_tree, hf_http2_headers, tvb, offset, -1, ENC_ASCII|ENC_NA);
|
|
offset += tvb_reported_length_remaining(tvb, offset);
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Priority */
|
|
static int
|
|
dissect_http2_priority(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_priority_r, tvb, offset, 4, ENC_NA);
|
|
proto_tree_add_item(http2_tree, hf_http2_priority, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* RST Stream */
|
|
static int
|
|
dissect_http2_rst_stream(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_rst_stream_error, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Settings */
|
|
static int
|
|
dissect_http2_settings(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
guint32 settingsid;
|
|
proto_item *ti_settings;
|
|
proto_tree *settings_tree;
|
|
|
|
while(tvb_reported_length_remaining(tvb, offset) > 0){
|
|
|
|
ti_settings = proto_tree_add_item(http2_tree, hf_http2_settings, tvb, offset, 8, ENC_NA);
|
|
settings_tree = proto_item_add_subtree(ti_settings, ett_http2_settings);
|
|
proto_tree_add_item(settings_tree, hf_http2_settings_r, tvb, offset, 1, ENC_NA);
|
|
offset +=1;
|
|
proto_tree_add_item(settings_tree, hf_http2_settings_identifier, tvb, offset, 3, ENC_NA);
|
|
settingsid = tvb_get_ntoh24(tvb, offset);
|
|
proto_item_append_text(ti_settings, " - %s", val_to_str( settingsid, http2_settings_vals, "Unknown (%u)") );
|
|
offset +=3;
|
|
|
|
|
|
switch(settingsid){
|
|
case HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
|
proto_tree_add_item(settings_tree, hf_http2_settings_max_concurrent_streams, tvb, offset, 4, ENC_NA);
|
|
break;
|
|
case HTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
|
|
proto_tree_add_item(settings_tree, hf_http2_settings_initial_window_size, tvb, offset, 4, ENC_NA);
|
|
break;
|
|
case HTTP2_SETTINGS_FLOW_CONTROL_OPTIONS:
|
|
proto_tree_add_item(settings_tree, hf_http2_settings_flow_control_options, tvb, offset, 4, ENC_NA);
|
|
break;
|
|
default:
|
|
proto_tree_add_item(settings_tree, hf_http2_settings_unknown, tvb, offset, 4, ENC_NA);
|
|
break;
|
|
}
|
|
proto_item_append_text(ti_settings, " : %u", tvb_get_ntohl(tvb, offset));
|
|
offset += 4;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Push Promise */
|
|
static int
|
|
dissect_http2_push_promise(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_push_promise_r, tvb, offset, 4, ENC_NA);
|
|
proto_tree_add_item(http2_tree, hf_http2_push_promise_promised_stream_id, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
|
|
/* TODO : Support header decompression */
|
|
proto_tree_add_item(http2_tree, hf_http2_push_promise_header, tvb, offset, -1, ENC_ASCII|ENC_NA);
|
|
offset += tvb_reported_length_remaining(tvb, offset);
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Ping */
|
|
static int
|
|
dissect_http2_ping(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags)
|
|
{
|
|
/* TODO : Add Response time */
|
|
if(flags & HTTP2_FLAGS_PO)
|
|
{
|
|
proto_tree_add_item(http2_tree, hf_http2_pong, tvb, offset, 8, ENC_NA);
|
|
}else{
|
|
proto_tree_add_item(http2_tree, hf_http2_ping, tvb, offset, 8, ENC_NA);
|
|
}
|
|
offset += 8;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Goaway */
|
|
static int
|
|
dissect_http2_goaway(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_goaway_r, tvb, offset, 4, ENC_NA);
|
|
proto_tree_add_item(http2_tree, hf_http2_goaway_last_stream_id, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_goaway_error, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
if(tvb_reported_length_remaining(tvb, offset) > 0)
|
|
{
|
|
proto_tree_add_item(http2_tree, hf_http2_goaway_addata , tvb, offset, -1, ENC_NA);
|
|
offset += tvb_reported_length_remaining(tvb, offset);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/* Window Update */
|
|
static int
|
|
dissect_http2_window_update(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_window_update_r, tvb, offset, 4, ENC_NA);
|
|
proto_tree_add_item(http2_tree, hf_http2_window_update_window_size_increment, tvb, offset, 4, ENC_NA);
|
|
offset += 4;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int
|
|
dissect_http2_continuation(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
|
|
{
|
|
|
|
/* TODO : Support "Reassemble Header" and header decompression */
|
|
proto_tree_add_item(http2_tree, hf_http2_continuation_header, tvb, offset, -1, ENC_ASCII|ENC_NA);
|
|
offset += tvb_reported_length_remaining(tvb, offset);
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int
|
|
dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_ )
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *http2_tree;
|
|
tvbuff_t *next_tvb;
|
|
guint offset = 0;
|
|
guint8 type, flags;
|
|
guint16 length;
|
|
guint32 streamid;
|
|
|
|
/* 4.1 Frame Format
|
|
0 1 2 3
|
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Length (16) | Type (8) | Flags (8) |
|
|
+-+-------------+---------------+-------------------------------+
|
|
|R| Stream Identifier (31) |
|
|
+-+-------------------------------------------------------------+
|
|
| Frame Payload (0...) ...
|
|
+---------------------------------------------------------------+
|
|
*/
|
|
ti = proto_tree_add_item(tree, hf_http2, tvb, 0, -1, ENC_NA);
|
|
|
|
http2_tree = proto_item_add_subtree(ti, ett_http2_header);
|
|
|
|
/* 3.5 Connection Header
|
|
Upon establishment of a TCP connection and determination that
|
|
HTTP/2.0 will be used by both peers, each endpoint MUST send a
|
|
connection header as a final confirmation and to establish the
|
|
initial settings for the HTTP/2.0 connection.
|
|
*/
|
|
/* tvb_memeql makes certain there are enough bytes in the buffer.
|
|
* returns -1 if there are not enough bytes or if there is not a
|
|
* match. Returns 0 on a match
|
|
*/
|
|
if (tvb_memeql(tvb, offset, kMagicHello, MAGIC_FRAME_LENGTH) == 0 )
|
|
{
|
|
col_append_sep_str( pinfo->cinfo, COL_INFO, ", ", "Magic" );
|
|
|
|
proto_item_set_len(ti, MAGIC_FRAME_LENGTH);
|
|
proto_item_append_text(ti, ": Magic");
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_magic, tvb, offset, MAGIC_FRAME_LENGTH, ENC_ASCII|ENC_NA);
|
|
return MAGIC_FRAME_LENGTH;
|
|
}
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_length, tvb, offset, 2, ENC_NA);
|
|
length = tvb_get_ntohs(tvb, offset);
|
|
offset += 2;
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_type, tvb, offset, 1, ENC_NA);
|
|
type = tvb_get_guint8(tvb, offset);
|
|
col_append_sep_fstr( pinfo->cinfo, COL_INFO, ", ", "%s", val_to_str(type, http2_type_vals, "Unknown type (%d)"));
|
|
|
|
offset += 1;
|
|
|
|
flags = dissect_http2_header_flags(tvb, pinfo, http2_tree, offset, type);
|
|
offset += 1;
|
|
|
|
proto_tree_add_item(http2_tree, hf_http2_r, tvb, offset, 4, ENC_NA);
|
|
proto_tree_add_item(http2_tree, hf_http2_streamid, tvb, offset, 4, ENC_NA);
|
|
streamid = tvb_get_ntohl(tvb, offset) & MASK_HTTP2_STREAMID;
|
|
proto_item_append_text(ti, ": %s, Stream ID: %u, Length %u", val_to_str(type, http2_type_vals, "Unknown type (%d)"), streamid, length);
|
|
offset += 4;
|
|
|
|
switch(type){
|
|
case HTTP2_DATA: /* Data (0) */
|
|
/* TODO: Enhance dissect (Display in text ?) */
|
|
next_tvb = tvb_new_subset_remaining(tvb, offset);
|
|
call_dissector(data_handle, next_tvb, pinfo, http2_tree);
|
|
break;
|
|
|
|
case HTTP2_HEADERS: /* Headers (1) */
|
|
dissect_http2_headers(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_PRIORITY: /* Priority (2) */
|
|
dissect_http2_priority(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_RST_STREAM: /* RST Stream (3) */
|
|
dissect_http2_rst_stream(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_SETTINGS: /* Settings (4) */
|
|
dissect_http2_settings(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_PUSH_PROMISE: /* PUSH Promise (5) */
|
|
dissect_http2_push_promise(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_PING: /* Ping (6) */
|
|
dissect_http2_ping(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_GOAWAY: /* Goaway (7) */
|
|
dissect_http2_goaway(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_WINDOW_UPDATE: /* Window Update (9) */
|
|
dissect_http2_window_update(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
case HTTP2_CONTINUATION: /* Continuation (10) */
|
|
dissect_http2_continuation(tvb, pinfo, http2_tree, offset, flags);
|
|
break;
|
|
|
|
default:
|
|
proto_tree_add_item(http2_tree, hf_http2_unknown, tvb, offset, -1, ENC_NA);
|
|
break;
|
|
}
|
|
return tvb_length(tvb);
|
|
}
|
|
|
|
|
|
static guint get_http2_message_len( packet_info *pinfo _U_, tvbuff_t *tvb, int offset )
|
|
{
|
|
if ( tvb_memeql( tvb, offset, kMagicHello, MAGIC_FRAME_LENGTH ) == 0 ) {
|
|
return MAGIC_FRAME_LENGTH;
|
|
}
|
|
|
|
return (guint)tvb_get_ntohs(tvb, offset) + FRAME_HEADER_LENGTH;
|
|
}
|
|
|
|
|
|
static int
|
|
dissect_http2(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
|
|
void *data)
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *http2_tree;
|
|
|
|
/* Check that there's enough data */
|
|
if (tvb_length(tvb) < FRAME_HEADER_LENGTH)
|
|
return 0;
|
|
|
|
col_set_str(pinfo->cinfo, COL_PROTOCOL, "HTTP2");
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
|
|
ti = proto_tree_add_item(tree, proto_http2, tvb, 0, -1, ENC_NA);
|
|
proto_item_append_text(ti, " (draft-06)");
|
|
|
|
http2_tree = proto_item_add_subtree(ti, ett_http2);
|
|
|
|
tcp_dissect_pdus(tvb, pinfo, http2_tree, TRUE, FRAME_HEADER_LENGTH,
|
|
get_http2_message_len, dissect_http2_pdu, data);
|
|
|
|
return tvb_length(tvb);
|
|
}
|
|
|
|
void
|
|
proto_register_http2(void)
|
|
{
|
|
|
|
static hf_register_info hf[] = {
|
|
/* Packet Header */
|
|
{ &hf_http2,
|
|
{ "Stream", "http2",
|
|
FT_NONE, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
{ &hf_http2_length,
|
|
{ "Length", "http2.length",
|
|
FT_UINT16, BASE_DEC, NULL, 0x0,
|
|
"The length of the frame payload (The 8 octets of the frame header are not included)", HFILL }
|
|
},
|
|
{ &hf_http2_type,
|
|
{ "Type", "http2.type",
|
|
FT_UINT8, BASE_DEC, VALS(http2_type_vals), 0x0,
|
|
"The frame type determines how the remainder of the frame header and payload are interpreted", HFILL }
|
|
},
|
|
{ &hf_http2_r,
|
|
{ "Reserved", "http2.r",
|
|
FT_UINT32, BASE_HEX, NULL, MASK_HTTP2_RESERVED,
|
|
"The semantics of this bit are undefined and the bit MUST remain unset (0) when sending and MUST be ignored when receiving", HFILL }
|
|
},
|
|
{ &hf_http2_streamid,
|
|
{ "Stream Identifier", "http2.streamid",
|
|
FT_UINT32, BASE_DEC, NULL, MASK_HTTP2_STREAMID,
|
|
"A 31-bit stream identifier", HFILL }
|
|
},
|
|
{ &hf_http2_magic,
|
|
{ "Magic", "http2.magic",
|
|
FT_STRING, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
{ &hf_http2_unknown,
|
|
{ "Unknown", "http2.unknown",
|
|
FT_BYTES, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
/* Flags */
|
|
{ &hf_http2_flags,
|
|
{ "Flags", "http2.flags",
|
|
FT_UINT8, BASE_HEX, NULL, 0x0,
|
|
"Flags are assigned semantics specific to the indicated frame type", HFILL }
|
|
},
|
|
{ &hf_http2_flags_end_stream,
|
|
{ "End Stream", "http2.flags.es",
|
|
FT_BOOLEAN, 8, NULL, HTTP2_FLAGS_ES,
|
|
"Set indicates that this frame is the last that the endpoint will send for the identified stream", HFILL }
|
|
},
|
|
{ &hf_http2_flags_end_headers,
|
|
{ "End Headers", "http2.flags.eh",
|
|
FT_BOOLEAN, 8, NULL, HTTP2_FLAGS_EH,
|
|
"Set indicates that this frame ends the sequence of header block fragments necessary to provide a complete set of headers", HFILL }
|
|
},
|
|
{ &hf_http2_flags_priority,
|
|
{ "Priority", "http2.flags.pr",
|
|
FT_BOOLEAN, 8, NULL, HTTP2_FLAGS_PR,
|
|
"Set indicates that the first four octets of this frame contain a single reserved bit and a 31-bit priority", HFILL }
|
|
},
|
|
{ &hf_http2_flags_end_push_promise,
|
|
{ "End Push Promise", "http2.flags.epp",
|
|
FT_BOOLEAN, 8, NULL, HTTP2_FLAGS_EPP,
|
|
"Set indicates that this frame ends the sequence of header block fragments necessary to provide a complete set of headers", HFILL }
|
|
},
|
|
{ &hf_http2_flags_pong,
|
|
{ "Pong", "http2.flags.po",
|
|
FT_BOOLEAN, 8, NULL, HTTP2_FLAGS_PO,
|
|
"Set indicates that this PING frame is a PING response", HFILL }
|
|
},
|
|
{ &hf_http2_flags_reserved,
|
|
{ "Reserved", "http2.flags.r",
|
|
FT_UINT8, BASE_HEX, NULL, HTTP2_FLAGS_R,
|
|
"(Must be zero)", HFILL }
|
|
},
|
|
{ &hf_http2_flags_reserved1,
|
|
{ "Reserved", "http2.flags.r1",
|
|
FT_UINT8, BASE_HEX, NULL, HTTP2_FLAGS_R1,
|
|
"(Must be zero)", HFILL }
|
|
},
|
|
{ &hf_http2_flags_reserved2,
|
|
{ "Reserved", "http2.flags.r2",
|
|
FT_UINT8, BASE_HEX, NULL, HTTP2_FLAGS_R2,
|
|
"(Must be zero)", HFILL }
|
|
},
|
|
{ &hf_http2_flags_reserved3,
|
|
{ "Reserved", "http2.flags.r3",
|
|
FT_UINT8, BASE_HEX, NULL, HTTP2_FLAGS_R3,
|
|
"(Must be zero)", HFILL }
|
|
},
|
|
{ &hf_http2_flags_reserved4,
|
|
{ "Reserved", "http2.flags.r4",
|
|
FT_UINT8, BASE_HEX, NULL, HTTP2_FLAGS_R4,
|
|
"(Must be zero)", HFILL }
|
|
},
|
|
/* Headers */
|
|
{ &hf_http2_headers_r,
|
|
{ "Reserved", "http2.headers.r",
|
|
FT_UINT32, BASE_HEX, NULL, MASK_HTTP2_RESERVED,
|
|
"Must be zero", HFILL }
|
|
},
|
|
{ &hf_http2_headers_priority,
|
|
{ "Priority", "http2.headers.priority",
|
|
FT_UINT32, BASE_DEC, NULL, MASK_HTTP2_PRIORITY,
|
|
"Priority for the stream (0 is the highest priority)", HFILL }
|
|
},
|
|
{ &hf_http2_headers,
|
|
{ "Headers", "http2.headers",
|
|
FT_STRING, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
|
|
/* Priority */
|
|
{ &hf_http2_priority_r,
|
|
{ "Reserved", "http2.priority.r",
|
|
FT_UINT32, BASE_HEX, NULL, MASK_HTTP2_RESERVED,
|
|
"Must be zero", HFILL }
|
|
},
|
|
{ &hf_http2_priority,
|
|
{ "Priority", "http2.priority",
|
|
FT_UINT32, BASE_DEC, NULL, MASK_HTTP2_PRIORITY,
|
|
"Priority for the stream (0 is the highest priority)", HFILL }
|
|
},
|
|
|
|
/* RST Stream */
|
|
{ &hf_http2_rst_stream_error,
|
|
{ "Error", "http2.rst_stream.error",
|
|
FT_UINT32, BASE_DEC, VALS(http2_error_codes_vals), 0x0,
|
|
"The error code indicates why the stream is being terminated", HFILL }
|
|
},
|
|
|
|
/* Settings */
|
|
{ &hf_http2_settings,
|
|
{ "Settings", "http2.settings",
|
|
FT_NONE, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
{ &hf_http2_settings_r,
|
|
{ "Reserved", "http2.settings.r",
|
|
FT_UINT8, BASE_HEX, NULL, 0x0,
|
|
"Must be zero", HFILL }
|
|
},
|
|
{ &hf_http2_settings_identifier,
|
|
{ "Settings Identifier", "http2.settings.id",
|
|
FT_UINT24, BASE_DEC, VALS(http2_settings_vals), 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
{ &hf_http2_settings_max_concurrent_streams,
|
|
{ "Max concurrent streams", "http2.settings.max_concurrent_streams",
|
|
FT_UINT32, BASE_DEC, NULL, 0x0,
|
|
"Indicates the maximum number of concurrent streams that the sender will allow", HFILL }
|
|
},
|
|
{ &hf_http2_settings_initial_window_size,
|
|
{ "Initial Windows Size", "http2.settings.initial_window_size",
|
|
FT_UINT32, BASE_DEC, NULL, 0x0,
|
|
"Indicates the sender's initial window size (in bytes) for stream level flow control", HFILL }
|
|
},
|
|
{ &hf_http2_settings_flow_control_options,
|
|
{ "Flow Control Options", "http2.settings.flow_control_options",
|
|
FT_UINT32, BASE_DEC, NULL, 0x0,
|
|
"Indicates flow control options", HFILL }
|
|
},
|
|
{ &hf_http2_settings_unknown,
|
|
{ "Unknown Settings", "http2.settings.unknown",
|
|
FT_UINT32, BASE_DEC, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
|
|
/* Push Promise */
|
|
{ &hf_http2_push_promise_r,
|
|
{ "Reserved", "http2.push_promise.r",
|
|
FT_UINT32, BASE_HEX, NULL, MASK_HTTP2_RESERVED,
|
|
"Must be zero", HFILL }
|
|
},
|
|
{ &hf_http2_push_promise_promised_stream_id,
|
|
{ "Promised-Stream-ID", "http2.push_promise.promised_stream_id",
|
|
FT_UINT32, BASE_DEC, NULL, MASK_HTTP2_PRIORITY,
|
|
"Identifies the stream the endpoint intends to start sending frames for", HFILL }
|
|
},
|
|
{ &hf_http2_push_promise_header,
|
|
{ "Header", "http2.push_promise.header",
|
|
FT_STRING, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
|
|
/* Ping / Pong */
|
|
{ &hf_http2_ping,
|
|
{ "Ping", "http2.ping",
|
|
FT_BYTES, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
{ &hf_http2_pong,
|
|
{ "Pong", "http2.pong",
|
|
FT_BYTES, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
|
|
/* Goaway */
|
|
{ &hf_http2_goaway_r,
|
|
{ "Reserved", "http2.goway.r",
|
|
FT_UINT32, BASE_HEX, NULL, MASK_HTTP2_RESERVED,
|
|
"Must be zero", HFILL }
|
|
},
|
|
{ &hf_http2_goaway_last_stream_id,
|
|
{ "Promised-Stream-ID", "http2.goaway.last_stream_id",
|
|
FT_UINT32, BASE_DEC, NULL, MASK_HTTP2_PRIORITY,
|
|
"Contains the highest numbered stream identifier for which the sender of the GOAWAY frame has received frames on and might have taken some action on", HFILL }
|
|
},
|
|
{ &hf_http2_goaway_error,
|
|
{ "Error", "http2.goaway.error",
|
|
FT_UINT32, BASE_DEC, VALS(http2_error_codes_vals), 0x0,
|
|
"The error code indicates the reason for closing the connection", HFILL }
|
|
},
|
|
{ &hf_http2_goaway_addata,
|
|
{ "Additional Debug Data", "http2.goaway.addata",
|
|
FT_BYTES, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL }
|
|
},
|
|
|
|
/* Window Update */
|
|
{ &hf_http2_window_update_r,
|
|
{ "Reserved", "http2.window_update.r",
|
|
FT_UINT32, BASE_HEX, NULL, MASK_HTTP2_RESERVED,
|
|
"Must be zero", HFILL }
|
|
},
|
|
{ &hf_http2_window_update_window_size_increment,
|
|
{ "Window Size Increment", "http2.window_update.window_size_increment",
|
|
FT_UINT32, BASE_DEC, NULL, MASK_HTTP2_PRIORITY,
|
|
"Indicating the number of bytes that the sender can transmit in addition to the existing flow control window", HFILL }
|
|
},
|
|
|
|
/* Continuation */
|
|
{ &hf_http2_continuation_header,
|
|
{ "Continuation Header", "http2.continuation.header",
|
|
FT_STRING, BASE_NONE, NULL, 0x0,
|
|
"Contains a header block fragment", HFILL }
|
|
},
|
|
|
|
};
|
|
|
|
static ei_register_info ei[] = {
|
|
{ &ei_http2_flags_epp_old, { "http2.flags.epp_old", PI_PROTOCOL, PI_WARN, "Deprecated End Push Promise flags (Now need to use 0x04)", EXPFILL }},
|
|
};
|
|
|
|
static gint *ett[] = {
|
|
&ett_http2,
|
|
&ett_http2_header,
|
|
&ett_http2_flags,
|
|
&ett_http2_settings
|
|
};
|
|
|
|
expert_module_t* expert_http2;
|
|
|
|
proto_http2 = proto_register_protocol("HyperText Transfer Protocol 2", "HTTP2", "http2");
|
|
|
|
proto_register_field_array(proto_http2, hf, array_length(hf));
|
|
proto_register_subtree_array(ett, array_length(ett));
|
|
expert_http2 = expert_register_protocol(proto_http2);
|
|
expert_register_field_array(expert_http2, ei, array_length(ei));
|
|
|
|
new_register_dissector("http2", dissect_http2, proto_http2);
|
|
}
|
|
|
|
void
|
|
proto_reg_handoff_http2(void)
|
|
{
|
|
data_handle = find_dissector("data");
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - http://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local variables:
|
|
* c-basic-offset: 4
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vi: set shiftwidth=4 tabstop=8 expandtab:
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
*/
|