Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tatsuhiro-t/11393062 to your computer and use it in GitHub Desktop.
Save tatsuhiro-t/11393062 to your computer and use it in GitHub Desktop.
[PATCH] Attempt to decode HTTP/2 header block using nghttp2 HPACK decoder
From db7d7ebfe6281835f33b74f1ece22acdc3b176bf Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Tue, 29 Apr 2014 16:28:41 +0900
Subject: [PATCH] Attempt to decode HTTP/2 header block using nghttp2 HPACK
decoder
In this patch, We use nghttp2 HPACK decoder to decompress HTTP/2 header
block. To make HPACK decompressor work, we need to track down HTTP/2
connection from the beginning. If we see the HTTP/2 magic (connection
preface), we initialize HPACK decompressor objects. We actually use
2 HPACK decompressor for both client and server. HPACK decompressor
objects are stored in hash tables using TCP stream index as a key.
This patch decompresses header block but just prints out in stderr.
We need to figure out the way to show these decoded headers in
GUI.
---
configure.ac | 12 +++
epan/Makefile.am | 2 +-
epan/dissectors/Makefile.am | 2 +-
epan/dissectors/packet-http2.c | 227 +++++++++++++++++++++++++++++++++++++++--
4 files changed, 234 insertions(+), 9 deletions(-)
diff --git a/configure.ac b/configure.ac
index 61dacf8..5258b8d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -672,6 +672,17 @@ esac
# FIXME: currently the path argument to with-libsmi is being ignored
AX_LIBSMI
+# libnghttp2
+# FIXME: Add option to disable nghttp2
+PKG_CHECK_MODULES([LIBNGHTTP2], [libnghttp2 >= 0.4.0],
+ [have_nghttp2=yes], [have_nghttp2=no])
+
+libnghttp2_message="no"
+if (test "${have_nghttp2}" = "yes"); then
+ libnghttp2_message="yes"
+fi
+
+
#
# Check for programs used when building DocBook documentation.
#
@@ -3086,3 +3097,4 @@ echo " Use POSIX capabilities library : $libcap_message"
echo " Use GeoIP library : $geoip_message"
echo " Use nl library : $libnl_message"
echo " Use SBC codec library : $have_sbc"
+echo " Use nghttp2 library : $libnghttp2_message"
diff --git a/epan/Makefile.am b/epan/Makefile.am
index dc54506..dd10b38 100644
--- a/epan/Makefile.am
+++ b/epan/Makefile.am
@@ -141,7 +141,7 @@ libwireshark_la_LIBADD = \
dissectors/libdirtydissectors.la dissectors/libfiledissectors.la \
wmem/libwmem.la $(wslua_lib) $(wspython_lib) @SOCKET_LIBS@ @NSL_LIBS@ \
@C_ARES_LIBS@ @ADNS_LIBS@ @LIBGCRYPT_LIBS@ @LIBGNUTLS_LIBS@ \
- @KRB5_LIBS@ @SSL_LIBS@ @LIBSMI_LDFLAGS@ @GEOIP_LIBS@ \
+ @KRB5_LIBS@ @SSL_LIBS@ @LIBSMI_LDFLAGS@ @GEOIP_LIBS@ @LIBNGHTTP2_LIBS@\
${top_builddir}/wiretap/libwiretap.la @GLIB_LIBS@ \
${top_builddir}/wsutil/libwsutil.la -lm
diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am
index 80a066c..b104902 100644
--- a/epan/dissectors/Makefile.am
+++ b/epan/dissectors/Makefile.am
@@ -21,7 +21,7 @@
noinst_LTLIBRARIES = libdirtydissectors.la libfiledissectors.la libdissectors.la
AM_CPPFLAGS = -I$(srcdir)/../.. -I$(srcdir)/.. \
- $(LIBGNUTLS_CFLAGS) $(LIBGCRYPT_CFLAGS)
+ $(LIBNGHTTP2_CFLAGS) $(LIBGNUTLS_CFLAGS) $(LIBGCRYPT_CFLAGS)
include Makefile.common
diff --git a/epan/dissectors/packet-http2.c b/epan/dissectors/packet-http2.c
index 1f63a10..b0644c3 100644
--- a/epan/dissectors/packet-http2.c
+++ b/epan/dissectors/packet-http2.c
@@ -37,14 +37,41 @@
#include "config.h"
+#include <stdio.h>
+
#include <glib.h>
+#include <nghttp2/nghttp2.h>
+
#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/expert.h>
+#include <epan/conversation.h>
+#include <epan/emem.h>
+#include <epan/follow.h>
#include "packet-tcp.h"
+/* struct to hold data per HTTP/2 session */
+typedef struct {
+ /* We need 2 inflater object for both client and server. Since
+ inflater object is symmetrical, we just want to know which
+ inflater is used for each TCP flow. The hd_inflater_first_flow
+ is used to select which one to use. Basically, we first record
+ fwd of tcp_analysis in hd_inflater_first_flow and if processing
+ packet_info has fwd of tcp_analysis equal to
+ hd_inflater_first_flow, we use hd_inflater[0], otherwise 2nd
+ one.
+ */
+ nghttp2_hd_inflater *hd_inflater[2];
+ tcp_flow_t *hd_inflater_first_flow;
+} http2_session_t;
+
+typedef struct {
+ /* Hash table, associates TCP stream index to http2_session_t object */
+ GHashTable *sessions;
+} http2_data_t;
+
void proto_register_http2(void);
void proto_reg_handoff_http2(void);
@@ -111,6 +138,7 @@ 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 int hf_http2_continuation_padding = -1;
/* Altsvc */
static int hf_http2_altsvc_maxage = -1;
static int hf_http2_altsvc_port = -1;
@@ -238,6 +266,153 @@ static const value_string http2_settings_vals[] = {
{ 0, NULL }
};
+static http2_session_t*
+create_http2_session(packet_info *pinfo)
+{
+ conversation_t *conversation;
+ http2_data_t *http2;
+ http2_session_t *h2session;
+ struct tcp_analysis *tcpd;
+ gint *key;
+
+ conversation = find_or_create_conversation(pinfo);
+
+ http2 = (http2_data_t*)conversation_get_proto_data(conversation,
+ proto_http2);
+
+ if(http2 == NULL) {
+ http2 = (http2_data_t*)se_alloc(sizeof(http2_data_t));
+
+ http2->sessions = g_hash_table_new(g_int_hash, g_int_equal);
+
+ conversation_add_proto_data(conversation, proto_http2, http2);
+ }
+
+ tcpd = get_tcp_conversation_data(NULL, pinfo);
+
+ h2session = (http2_session_t*)se_alloc(sizeof(http2_session_t));
+ nghttp2_hd_inflate_new(&h2session->hd_inflater[0]);
+ nghttp2_hd_inflate_new(&h2session->hd_inflater[1]);
+ h2session->hd_inflater_first_flow = tcpd->fwd;
+
+ key = (gint*)se_alloc(sizeof(gint));
+
+ *key = tcpd->stream;
+
+ g_hash_table_insert(http2->sessions, key, h2session);
+
+ return h2session;
+}
+
+static http2_session_t*
+get_http2_session(packet_info *pinfo)
+{
+ conversation_t *conversation;
+ http2_data_t *http2;
+ http2_session_t *h2session;
+ struct tcp_analysis *tcpd;
+ gint key;
+
+ conversation = find_or_create_conversation(pinfo);
+
+ if(!conversation) {
+ fprintf(stderr, "warn: conversation is NULL\n");
+ return NULL;
+ }
+
+ http2 = (http2_data_t*)conversation_get_proto_data(conversation,
+ proto_http2);
+
+ if(!http2) {
+ fprintf(stderr, "warn: http2 is null\n");
+ return NULL;
+ }
+
+ tcpd = get_tcp_conversation_data(NULL, pinfo);
+
+ key = tcpd->stream;
+ h2session = (http2_session_t*)g_hash_table_lookup(http2->sessions, &key);
+
+ if(!h2session) {
+ fprintf(stderr, "warn: h2session is NULL\n");
+ }
+
+ return h2session;
+}
+
+static nghttp2_hd_inflater*
+select_http2_hd_inflater(packet_info *pinfo, http2_session_t *h2session)
+{
+ struct tcp_analysis *tcpd;
+
+ tcpd = get_tcp_conversation_data(NULL, pinfo);
+
+ if(tcpd->fwd == h2session->hd_inflater_first_flow) {
+ return h2session->hd_inflater[0];
+ } else {
+ return h2session->hd_inflater[1];
+ }
+}
+
+static void
+inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
+ size_t headlen,
+ http2_session_t *h2session, guint8 flags)
+{
+ uint8_t *headbuf;
+ nghttp2_hd_inflater *hd_inflater;
+ int rv;
+ int final;
+
+ if(!h2session) {
+ /* We may not be able to track all HTTP/2 session if we miss
+ first magic (connection preface) */
+ return;
+ }
+
+ headbuf = (uint8_t*)se_alloc(headlen);
+ tvb_memcpy(tvb, headbuf, offset, headlen);
+
+ hd_inflater = select_http2_hd_inflater(pinfo, h2session);
+
+ final = flags & HTTP2_FLAGS_END_HEADERS;
+
+ fprintf(stderr, "Decoding header block:\n");
+
+ for(;;) {
+ nghttp2_nv nv;
+ int inflate_flags = 0;
+
+ rv = nghttp2_hd_inflate_hd(hd_inflater, &nv,
+ &inflate_flags, headbuf, headlen, final);
+
+ if(rv < 0) {
+ fprintf(stderr, "inflate failed with error code %d", rv);
+ break;
+ }
+
+ headbuf += rv;
+ headlen -= rv;
+
+ if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ fwrite(nv.name, nv.namelen, 1, stderr);
+ fprintf(stderr, ": ");
+ fwrite(nv.value, nv.valuelen, 1, stderr);
+ fprintf(stderr, "\n");
+ }
+ if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+ nghttp2_hd_inflate_end_headers(hd_inflater);
+ break;
+ }
+ if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 &&
+ headlen == 0) {
+ break;
+ }
+ }
+
+ fprintf(stderr, "\n");
+}
+
static guint8
dissect_http2_header_flags(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 type)
{
@@ -370,6 +545,9 @@ dissect_http2_headers(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_t
{
guint16 padding;
gint headlen;
+ http2_session_t *h2session;
+
+ h2session = get_http2_session(pinfo);
offset = dissect_frame_padding(tvb, &padding, http2_tree, offset, flags);
offset = dissect_frame_prio(tvb, http2_tree, offset, flags);
@@ -377,6 +555,9 @@ dissect_http2_headers(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_t
/* TODO : Support header decompression */
headlen = tvb_reported_length_remaining(tvb, offset) - padding;
proto_tree_add_item(http2_tree, hf_http2_headers, tvb, offset, headlen, ENC_ASCII|ENC_NA);
+
+ inflate_http2_header_block(tvb, pinfo, offset, headlen, h2session, flags);
+
offset += headlen;
proto_tree_add_item(http2_tree, hf_http2_headers_padding, tvb, offset, padding, ENC_NA);
@@ -415,6 +596,10 @@ dissect_http2_settings(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_
proto_item *ti_settings;
proto_tree *settings_tree;
+ /* FIXME: If we send SETTINGS_HEADER_TABLE_SIZE, aAfter receiving
+ ACK from peer, we have to apply its value to HPACK decoder
+ using nghttp2_hd_inflate_change_table_size() */
+
while(tvb_reported_length_remaining(tvb, offset) > 0){
ti_settings = proto_tree_add_item(http2_tree, hf_http2_settings, tvb, offset, 5, ENC_NA);
@@ -459,7 +644,10 @@ dissect_http2_push_promise(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *ht
guint offset, guint8 flags _U_)
{
guint16 padding;
- gint headerfrag;
+ gint headlen;
+ http2_session_t *h2session;
+
+ h2session = get_http2_session(pinfo);
offset = dissect_frame_padding(tvb, &padding, http2_tree, offset, flags);
@@ -469,11 +657,13 @@ dissect_http2_push_promise(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *ht
offset += 4;
/* TODO : Support header decompression */
- headerfrag = tvb_reported_length_remaining(tvb, offset) - padding;
- proto_tree_add_item(http2_tree, hf_http2_push_promise_header, tvb, offset, headerfrag,
+ headlen = tvb_reported_length_remaining(tvb, offset) - padding;
+ proto_tree_add_item(http2_tree, hf_http2_push_promise_header, tvb, offset, headlen,
ENC_ASCII|ENC_NA);
- offset += headerfrag;
+ inflate_http2_header_block(tvb, pinfo, offset, headlen, h2session, flags);
+
+ offset += headlen;
proto_tree_add_item(http2_tree, hf_http2_push_promise_padding, tvb,
offset, padding, ENC_NA);
@@ -532,12 +722,27 @@ dissect_http2_window_update(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *h
}
static int
-dissect_http2_continuation(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_)
+dissect_http2_continuation(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags)
{
+ guint16 padding;
+ gint headlen;
+ http2_session_t *h2session;
+
+ h2session = get_http2_session(pinfo);
+
+ offset = dissect_frame_padding(tvb, &padding, http2_tree, offset, flags);
/* 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);
+ headlen = tvb_reported_length_remaining(tvb, offset) - padding;
+ proto_tree_add_item(http2_tree, hf_http2_continuation_header, tvb, offset, headlen, ENC_ASCII|ENC_NA);
+
+ inflate_http2_header_block(tvb, pinfo, offset, headlen, h2session, flags);
+
+ offset += headlen;
+
+ proto_tree_add_item(http2_tree, hf_http2_continuation_padding, tvb, offset, padding, ENC_NA);
+
+ offset += padding;
return offset;
}
@@ -630,6 +835,9 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
proto_item_append_text(ti, ": Magic");
proto_tree_add_item(http2_tree, hf_http2_magic, tvb, offset, MAGIC_FRAME_LENGTH, ENC_ASCII|ENC_NA);
+
+ create_http2_session(pinfo);
+
return MAGIC_FRAME_LENGTH;
}
@@ -1032,6 +1240,11 @@ proto_register_http2(void)
FT_STRING, BASE_NONE, NULL, 0x0,
"Contains a header block fragment", HFILL }
},
+ { &hf_http2_continuation_padding,
+ { "Padding", "http2.continuation.padding",
+ FT_BYTES, BASE_NONE, NULL, 0x0,
+ "Padding octets", HFILL }
+ },
/* Altsvc */
{ &hf_http2_altsvc_maxage,
--
1.9.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment