Created
April 29, 2014 07:35
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From db7d7ebfe6281835f33b74f1ece22acdc3b176bf Mon Sep 17 00:00:00 2001 | |
From: Tatsuhiro Tsujikawa <[email protected]> | |
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