1 /* packet-grpc.c
2  * Routines for GRPC dissection
3  * Copyright 2017, Huang Qiangxiong <qiangxiong.huang@qq.com>
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * SPDX-License-Identifier: GPL-2.0-or-later
10  */
11 
12 /*
13 * The information used comes from:
14 * https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
15 *
16 * This GRPC dissector must be invoked by HTTP2 dissector.
17 *
18 * The main task of grpc dissector includes:
19 *
20 * 1. Parse grpc message header first, if header shows message is compressed,
21 *    it will find grpc-encoding http2 header by invoking http2_get_header_value()
22 *    and uncompress the following message body according to the value of
23 *    grpc-encoding header. After that grpc dissector call subdissector
24 *    to dissect the (uncompressed) data of message body.
25 *
26 * 2. GRPC dissector will create and maintain a new dissector table named
27 *    'grpc_message_type'. It allows dissection of a grpc message body.
28 *    The pattern format used by this table has two levels:
29 *
30 *    1) Request/Response level pattern, which includes request
31 *       grpc-method-path (equals to http2 ':path' header value) and
32 *       direction (request or response), the format:
33 *           http2-content-type "," http2-path "," direction
34 *       direction = "request" / "response",    for example:
35 *           "application/grpc,/helloworld.Greeter/SayHello,request"
36 *       The "helloworld.Greeter" is  grpc_package "." grpc_service
37 *
38 *    2) Content-type level pattern, which just takes http2-content-type
39 *       as pattern (for example, "application/grpc",
40 *       "application/grpc+proto" and "application/grpc+json").
41 *
42 *    GRPC dissector will try to call request/response message level
43 *    subdissector first. If not found, then try content-type level
44 *    dissectors. grpc dissector will always transmit grpc message
45 *    information - (http2-content-type "," http2-path "," direction ) to
46 *    subdissector in (void *data) parameter of dissect handler.
47 *    Content-type level subdissector can use this information to locate
48 *    the request/response message type.
49 *
50 *
51 * TODO
52 *   Support tap.
53 *   Support statistics.
54 */
55 
56 #include "config.h"
57 
58 #include <epan/packet.h>
59 #include <epan/expert.h>
60 #include <epan/prefs.h>
61 #include <epan/proto_data.h>
62 #include <epan/dissectors/packet-http2.h>
63 
64 #include "wsutil/pint.h"
65 
66 #define GRPC_MESSAGE_HEAD_LEN 5
67 
68 /* http2 standard headers */
69 #define HTTP2_HEADER_PATH ":path"
70 #define HTTP2_HEADER_CONTENT_TYPE "content-type"
71 /* http2 for grpc */
72 #define HTTP2_HEADER_GRPC_ENCODING "grpc-encoding"
73 
74 /*
75 * Decompression of zlib encoded entities.
76 */
77 #ifdef HAVE_ZLIB
78 static gboolean grpc_decompress_body = TRUE;
79 #else
80 static gboolean grpc_decompress_body = FALSE;
81 #endif
82 
83 /* detect json automatically */
84 static gboolean grpc_detect_json_automatically = TRUE;
85 /* whether embed GRPC messages under HTTP2 protocol tree items */
86 static gboolean grpc_embedded_under_http2 = FALSE;
87 
88 void proto_register_grpc(void);
89 void proto_reg_handoff_grpc(void);
90 
91 static int proto_grpc = -1;
92 
93 /* message header */
94 static int hf_grpc_compressed_flag = -1;
95 static int hf_grpc_message_length = -1;
96 /* message body */
97 static int hf_grpc_message_data = -1;
98 
99 /* compressed flag vals */
100 #define grpc_compressed_flag_vals_VALUE_STRING_LIST(XXX)    \
101     XXX(GRPC_NOT_COMPRESSED, 0, "Not Compressed")  \
102     XXX(GRPC_COMPRESSED, 1, "Compressed")
103 
104 VALUE_STRING_ENUM(grpc_compressed_flag_vals);
105 VALUE_STRING_ARRAY(grpc_compressed_flag_vals);
106 
107 /* expert */
108 static expert_field ei_grpc_body_decompression_failed = EI_INIT;
109 static expert_field ei_grpc_body_malformed = EI_INIT;
110 
111 /* trees */
112 static int ett_grpc = -1;
113 static int ett_grpc_message = -1;
114 static int ett_grpc_encoded_entity = -1;
115 
116 static dissector_handle_t grpc_handle;
117 
118 /* GRPC message type dissector table list.
119 * Dissectors can register themselves in this table as grpc message data dissectors.
120 * Dissectors registered in this table may use pattern that
121 * contains content-type,grpc-method-path(http2_path),request/response info, like:
122 *     application/grpc,/helloworld.Greeter/SayHello,request
123 * or just contains content-type:
124 *     application/grpc
125 *     application/grpc+proto
126 *     application/grpc+json
127 */
128 static dissector_table_t grpc_message_type_subdissector_table;
129 
130 /* Try to dissect grpc message according to grpc message info or http2 content_type. */
131 static void
dissect_body_data(proto_tree * grpc_tree,packet_info * pinfo,tvbuff_t * tvb,const gint offset,gint length,gboolean continue_dissect,const gchar * http2_path,gboolean is_request)132 dissect_body_data(proto_tree *grpc_tree, packet_info *pinfo, tvbuff_t *tvb, const gint offset,
133     gint length, gboolean continue_dissect,
134     const gchar* http2_path, gboolean is_request)
135 {
136     const gchar *http2_content_type;
137     gchar *grpc_message_info;
138     tvbuff_t *next_tvb;
139     int dissected;
140     proto_tree *parent_tree;
141 
142     proto_tree_add_bytes_format_value(grpc_tree, hf_grpc_message_data, tvb, offset, length, NULL, "%u bytes", length);
143 
144     if (!continue_dissect) {
145         return; /* if uncompress failed, we don't continue dissecting. */
146     }
147 
148     http2_content_type = http2_get_header_value(pinfo, HTTP2_HEADER_CONTENT_TYPE, FALSE);
149     if (http2_content_type == NULL || http2_path == NULL) {
150         return; /* not continue if there is not enough grpc information */
151     }
152 
153     next_tvb = tvb_new_subset_length(tvb, offset, length);
154 
155     /* Try to detect body as json first.
156     * Current grpc-java version sends json on grpc with content-type = application/grpc
157     * insteadof application/grpc+json, so we may detect to dissect message with default
158     * content-type application/grpc by json dissector insteadof protobuf dissector.
159     */
160     if (grpc_detect_json_automatically && length > 3
161         && tvb_get_guint8(next_tvb, 0) == '{')  /* start with '{' */
162     {
163         guint8 end_bytes[3];
164         tvb_memcpy(next_tvb, end_bytes, length - 3, 3);
165         if (end_bytes[2] == '}'     /* end with '}' */
166             || end_bytes[1] == '}'  /* or "}\n" */
167             || end_bytes[0] == '}') /* or "}\n\r" or " }\r\n" */
168         {
169             /* We just replace content-type with "application/grpc+json" insteadof calling
170             JSON dissector directly. Because someone may want to use his own dissector to
171             parse json insteadof default json dissector. */
172             http2_content_type = "application/grpc+json";
173         }
174     }
175 
176     /* Since message data (like protobuf) may be not a self-describing protocol, we need
177     * provide grpc service-name, method-name and request or response type to subdissector.
178     * According to these information, subdissector may find correct message definition
179     * from IDL file like ".proto".
180     *
181     * We define a string format to carry these information. The benefit using string is
182     * the grpc message information might be used by the other Lua dissector in the future.
183     * The grpc message information format is:
184     *   http2_content_type "," http2_path "," ("request" / "response")
185     * Acording to grpc wire format guide, it will be:
186     *   "application/grpc" [("+proto" / "+json" / {custom})] "," "/" service-name "/" method-name "/" "," ("request" / "response")
187     * For example:
188     *   application/grpc,/helloworld.Greeter/SayHello,request
189     */
190     grpc_message_info = wmem_strconcat(pinfo->pool, http2_content_type, ",",
191         http2_path, ",", (is_request ? "request" : "response"), NULL);
192 
193     parent_tree = proto_tree_get_parent_tree(grpc_tree);
194 
195     /* Protobuf dissector may be implemented that each request or response message
196     * of a method is defined as an individual dissector, so we try dissect using
197     * grpc_message_info first.
198     */
199     dissected = dissector_try_string(grpc_message_type_subdissector_table, grpc_message_info,
200         next_tvb, pinfo, parent_tree, grpc_message_info);
201 
202     if (dissected == 0) {
203         /* not dissected yet, we try common subdissector again. */
204         dissector_try_string(grpc_message_type_subdissector_table, http2_content_type,
205             next_tvb, pinfo, parent_tree, grpc_message_info);
206     }
207 }
208 
209 static gboolean
can_uncompress_body(packet_info * pinfo,const gchar ** compression_method)210 can_uncompress_body(packet_info *pinfo, const gchar **compression_method)
211 {
212     const gchar *grpc_encoding = http2_get_header_value(pinfo, HTTP2_HEADER_GRPC_ENCODING, FALSE);
213     *compression_method = grpc_encoding;
214 
215     /* check http2 have a grpc-encoding header appropriate */
216     return grpc_decompress_body
217         && grpc_encoding != NULL
218         && (strcmp(grpc_encoding, "gzip") == 0 || strcmp(grpc_encoding, "deflate") == 0);
219 }
220 
221 /* Dissect a grpc message. The caller needs to guarantee that the length is equal
222 to 5 + message_length according to grpc wire format definition. */
223 static guint
dissect_grpc_message(tvbuff_t * tvb,guint offset,guint length,packet_info * pinfo,proto_tree * grpc_tree,const gchar * http2_path,gboolean is_request)224 dissect_grpc_message(tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo, proto_tree *grpc_tree,
225                      const gchar* http2_path, gboolean is_request)
226 {
227     guint32 compressed_flag, message_length;
228     const gchar *compression_method;
229 
230     /* GRPC message format:
231     Delimited-Message -> Compressed-Flag Message-Length Message
232     Compressed-Flag -> 0 / 1 # encoded as 1 byte unsigned integer
233     Message-Length -> {length of Message} # encoded as 4 byte unsigned integer
234     Message -> *{binary octet} (may be protobuf or json)
235     */
236     proto_tree_add_item_ret_uint(grpc_tree, hf_grpc_compressed_flag, tvb, offset, 1, ENC_BIG_ENDIAN, &compressed_flag);
237     offset += 1;
238 
239     proto_tree_add_item(grpc_tree, hf_grpc_message_length, tvb, offset, 4, ENC_BIG_ENDIAN);
240     message_length = length - 5;  /* should be equal to tvb_get_ntohl(tvb, offset) */
241     offset += 4;
242 
243     if (message_length == 0) {
244         return offset;
245     }
246 
247     /* uncompressed message data if compressed_flag is set */
248     if (compressed_flag & GRPC_COMPRESSED) {
249         if (can_uncompress_body(pinfo, &compression_method)) {
250             proto_item *compressed_proto_item = NULL;
251             tvbuff_t *uncompressed_tvb = tvb_child_uncompress(tvb, tvb, offset, message_length);
252 
253             proto_tree *compressed_entity_tree = proto_tree_add_subtree_format(
254                 grpc_tree, tvb, offset, message_length, ett_grpc_encoded_entity,
255                 &compressed_proto_item, "Message-encoded entity body (%s): %u bytes",
256                 compression_method == NULL ? "unknown" : compression_method, message_length
257             );
258 
259             if (uncompressed_tvb != NULL) {
260                 guint uncompressed_length = tvb_captured_length(uncompressed_tvb);
261                 add_new_data_source(pinfo, uncompressed_tvb, "Uncompressed entity body");
262                 proto_item_append_text(compressed_proto_item, " -> %u bytes", uncompressed_length);
263                 dissect_body_data(grpc_tree, pinfo, uncompressed_tvb, 0, uncompressed_length, TRUE,
264                     http2_path, is_request);
265             } else {
266                 proto_tree_add_expert(compressed_entity_tree, pinfo, &ei_grpc_body_decompression_failed,
267                     tvb, offset, message_length);
268                 dissect_body_data(grpc_tree, pinfo, tvb, offset, message_length, FALSE, http2_path, is_request);
269             }
270         } else { /* compressed flag is set, but we can not uncompressed */
271             dissect_body_data(grpc_tree, pinfo, tvb, offset, message_length, FALSE, http2_path, is_request);
272         }
273     } else {
274         dissect_body_data(grpc_tree, pinfo, tvb, offset, message_length, TRUE, http2_path, is_request);
275     }
276 
277     return offset + message_length;
278 }
279 
280 static int
dissect_grpc(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)281 dissect_grpc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
282 {
283     proto_item *ti;
284     proto_tree *grpc_tree;
285     guint32 message_length;
286     guint offset = 0;
287     const gchar* http2_path;
288     gboolean is_request;
289     guint tvb_len = tvb_reported_length(tvb);
290 
291     if (!grpc_embedded_under_http2 && proto_tree_get_parent_tree(tree)) {
292         tree = proto_tree_get_parent_tree(tree);
293     }
294 
295     /* http2 had reassembled the http2.data.data, so we need not reassemble again.
296     reassembled http2.data.data may contain one or more grpc messages. */
297     while (offset < tvb_len)
298     {
299         if (tvb_len - offset < GRPC_MESSAGE_HEAD_LEN) {
300             /* need at least 5 bytes for dissecting a grpc message */
301             if (pinfo->can_desegment) {
302                 pinfo->desegment_offset = offset;
303                 pinfo->desegment_len = GRPC_MESSAGE_HEAD_LEN - (tvb_len - offset);
304                 return offset;
305             }
306             proto_tree_add_expert_format(tree, pinfo, &ei_grpc_body_malformed, tvb, offset, -1,
307                      "GRPC Malformed message data: only %u bytes left, need at least %u bytes.", tvb_len - offset, GRPC_MESSAGE_HEAD_LEN);
308             break;
309         }
310 
311         message_length = tvb_get_ntohl(tvb, offset + 1);
312         if (tvb_len - offset < GRPC_MESSAGE_HEAD_LEN + message_length) {
313             /* remaining bytes are not enough for dissecting the message body */
314             if (pinfo->can_desegment) {
315                 pinfo->desegment_offset = offset;
316                 pinfo->desegment_len = GRPC_MESSAGE_HEAD_LEN + message_length - (tvb_len - offset);
317                 return offset;
318             }
319             proto_tree_add_expert_format(tree, pinfo, &ei_grpc_body_malformed, tvb, offset, -1,
320                      "GRPC Malformed message data: only %u bytes left, need at least %u bytes.", tvb_len - offset, GRPC_MESSAGE_HEAD_LEN + message_length);
321             break;
322         }
323         /* ready to add information into protocol columns and tree */
324         if (offset == 0) { /* change columns only when there is at least one grpc message will be parsed */
325             col_set_str(pinfo->cinfo, COL_PROTOCOL, "GRPC");
326             col_append_str(pinfo->cinfo, COL_INFO, " (GRPC)");
327             col_set_fence(pinfo->cinfo, COL_PROTOCOL);
328         }
329         ti = proto_tree_add_item(tree, proto_grpc, tvb, offset, message_length + GRPC_MESSAGE_HEAD_LEN, ENC_NA);
330         grpc_tree = proto_item_add_subtree(ti, ett_grpc_message);
331 
332         /* http2_path contains: "/" Service-Name "/" {method name} */
333         http2_path = http2_get_header_value(pinfo, HTTP2_HEADER_PATH, FALSE);
334         is_request = (http2_path != NULL);
335 
336         if (http2_path == NULL) { /* this response, so we get it from http2 request stream */
337             http2_path = http2_get_header_value(pinfo, HTTP2_HEADER_PATH, TRUE);
338         }
339 
340         if (http2_path) {
341             proto_item_append_text(ti, ": %s, %s", http2_path, (is_request ? "Request" : "Response"));
342         }
343 
344         offset = dissect_grpc_message(tvb, offset, GRPC_MESSAGE_HEAD_LEN + message_length, pinfo, grpc_tree, http2_path, is_request);
345     }
346 
347     return tvb_captured_length(tvb);
348 }
349 
350 void
proto_register_grpc(void)351 proto_register_grpc(void)
352 {
353 
354     static hf_register_info hf[] = {
355         { &hf_grpc_compressed_flag,
356         { "Compressed Flag", "grpc.compressed_flag",
357         FT_UINT8, BASE_DEC, VALS(grpc_compressed_flag_vals), 0x0,
358         "Compressed-Flag value of 1 indicates that the binary octet sequence of Message is compressed", HFILL }
359         },
360         { &hf_grpc_message_length,
361         { "Message Length", "grpc.message_length",
362         FT_UINT32, BASE_DEC, NULL, 0x0,
363         "The length (32 bits) of message payload (not include itself)", HFILL }
364         },
365         { &hf_grpc_message_data,
366         { "Message Data", "grpc.message_data",
367         FT_BYTES, BASE_NONE, NULL, 0x0,
368         NULL, HFILL }
369         }
370     };
371 
372     static gint *ett[] = {
373         &ett_grpc,
374         &ett_grpc_message,
375         &ett_grpc_encoded_entity
376     };
377 
378     /* Setup protocol expert items */
379     static ei_register_info ei[] = {
380         { &ei_grpc_body_decompression_failed,
381         { "grpc.body_decompression_failed", PI_UNDECODED, PI_WARN,
382         "Body decompression failed", EXPFILL }
383         },
384         { &ei_grpc_body_malformed,
385         { "grpc.body_malformed", PI_UNDECODED, PI_WARN,
386         "Malformed message data", EXPFILL }
387         }
388     };
389 
390     module_t *grpc_module;
391     expert_module_t *expert_grpc;
392 
393     proto_grpc = proto_register_protocol("GRPC Message", "GRPC", "grpc");
394 
395     proto_register_field_array(proto_grpc, hf, array_length(hf));
396     proto_register_subtree_array(ett, array_length(ett));
397 
398     grpc_module = prefs_register_protocol(proto_grpc, proto_reg_handoff_grpc);
399 
400     prefs_register_bool_preference(grpc_module, "detect_json_automatically",
401         "Always check whether the message is JSON regardless of content-type.",
402         "Normally application/grpc message is protobuf, "
403         "but sometime the true message is json. "
404         "If this option in on, we always check whether the message is JSON "
405         "(body starts with '{' and ends with '}') regardless of "
406         "grpc_message_type_subdissector_table settings (which dissect grpc "
407         "message according to content-type).",
408         &grpc_detect_json_automatically);
409 
410     prefs_register_bool_preference(grpc_module, "embedded_under_http2",
411         "Embed gRPC messages under HTTP2 protocol tree items.",
412         "Embed gRPC messages under HTTP2 protocol tree items.",
413         &grpc_embedded_under_http2);
414 
415     prefs_register_static_text_preference(grpc_module, "service_definition",
416         "Please refer to preferences of Protobuf for specifying gRPC Service Definitions (*.proto).",
417         "Including specifying .proto files search paths, etc.");
418 
419     expert_grpc = expert_register_protocol(proto_grpc);
420     expert_register_field_array(expert_grpc, ei, array_length(ei));
421 
422     grpc_handle = register_dissector("grpc", dissect_grpc, proto_grpc);
423 
424     /*
425     * Dissectors can register themselves in this table as grpc message
426     * subdissector. Default it support json, protobuf.
427     */
428     grpc_message_type_subdissector_table =
429         register_dissector_table("grpc_message_type",
430             "GRPC message type", proto_grpc, FT_STRING, BASE_NONE);
431 }
432 
433 void
proto_reg_handoff_grpc(void)434 proto_reg_handoff_grpc(void)
435 {
436     char *content_types[] = {
437         "application/grpc",
438         "application/grpc+proto",
439         "application/grpc+json",
440         NULL /* end flag */
441     };
442     int i;
443 
444     /* register/deregister grpc_handle to/from tables */
445     for (i = 0; content_types[i]; i++) {
446         dissector_add_string("streaming_content_type", content_types[i], grpc_handle);
447         dissector_add_string("media_type", content_types[i], grpc_handle);
448     }
449 }
450 
451 /*
452 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
453 *
454 * Local variables:
455 * c-basic-offset: 4
456 * tab-width: 8
457 * indent-tabs-mode: nil
458 * End:
459 *
460 * vi: set shiftwidth=4 tabstop=8 expandtab:
461 * :indentSize=4:tabSize=8:noTabs=true:
462 */
463