diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /epan/dissectors/packet-grpc.c | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'epan/dissectors/packet-grpc.c')
-rw-r--r-- | epan/dissectors/packet-grpc.c | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/epan/dissectors/packet-grpc.c b/epan/dissectors/packet-grpc.c new file mode 100644 index 00000000..a459fde2 --- /dev/null +++ b/epan/dissectors/packet-grpc.c @@ -0,0 +1,575 @@ +/* packet-grpc.c + * Routines for GRPC dissection + * Copyright 2017,2022 Huang Qiangxiong <qiangxiong.huang@qq.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* +* The information used comes from: +* https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md +* https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md +* +* This GRPC dissector must be invoked by HTTP2 or HTTP dissector. +* The native GRPC is always over HTTP2, the GRPC-Web is over either HTTP2 or HTTP. +* +* The main task of GRPC dissector for native GRPC includes: +* +* 1. Parse grpc message header first, if header shows message is compressed, +* it will find grpc-encoding http2 header by invoking http2_get_header_value() +* and uncompress the following message body according to the value of +* grpc-encoding header. After that grpc dissector call subdissector +* to dissect the (uncompressed) data of message body. +* +* 2. GRPC dissector will create and maintain a new dissector table named +* 'grpc_message_type'. It allows dissection of a grpc message body. +* The pattern format used by this table has two levels: +* +* 1) Request/Response level pattern, which includes request +* grpc-method-path (equals to http2 ':path' header value) and +* direction (request or response), the format: +* http2-content-type "," http2-path "," direction +* direction = "request" / "response", for example: +* "application/grpc,/helloworld.Greeter/SayHello,request" +* The "helloworld.Greeter" is grpc_package "." grpc_service +* +* 2) Content-type level pattern, which just takes http2-content-type +* as pattern (for example, "application/grpc", +* "application/grpc+proto" and "application/grpc+json"). +* +* GRPC dissector will try to call request/response message level +* subdissector first. If not found, then try content-type level +* dissectors. grpc dissector will always transmit grpc message +* information - (http2-content-type "," http2-path "," direction ) to +* subdissector in (void *data) parameter of dissect handler. +* Content-type level subdissector can use this information to locate +* the request/response message type. +* +* For GRPC-WEB, the ways to get information like content-type, path (request uri) +* are different. And for GRPC-WEB-TEXT, the dissector will first decode the base64 +* payload and then dissect the data as GRPC-WEB. +*/ + +#include "config.h" + +#include <epan/conversation.h> +#include <epan/proto_data.h> +#include <epan/packet.h> +#include <epan/expert.h> +#include <epan/prefs.h> +#include <epan/strutil.h> +#include <epan/proto_data.h> +#include "packet-http.h" +#include "packet-http2.h" +#include "packet-media-type.h" + +#include "wsutil/pint.h" + +#define GRPC_MESSAGE_HEAD_LEN 5 + +/* calculate the size of a bytes after decoding as base64 */ +#define BASE64_ENCODE_SIZE(len) ((len) / 3 * 4 + ((len) % 3 == 0 ? 0 : 4)) + +/* +* Decompression of zlib encoded entities. +*/ +#ifdef HAVE_ZLIB +static gboolean grpc_decompress_body = TRUE; +#else +static gboolean grpc_decompress_body = FALSE; +#endif + +/* detect json automatically */ +static gboolean grpc_detect_json_automatically = TRUE; +/* whether embed GRPC messages under HTTP2 (or other) protocol tree items */ +static gboolean grpc_embedded_under_http2 = FALSE; + +void proto_register_grpc(void); +void proto_reg_handoff_grpc(void); + +static int proto_grpc = -1; +static int proto_http = -1; + +/* message header */ +static int hf_grpc_frame_type = -1; +static int hf_grpc_compressed_flag = -1; +static int hf_grpc_message_length = -1; +/* message body */ +static int hf_grpc_message_data = -1; + +/* grpc protocol type */ +#define grpc_protocol_type_vals_VALUE_STRING_LIST(XXX) \ + XXX(GRPC_PTYPE_GRPC, 0, "GRPC") \ + XXX(GRPC_PTYPE_GRPC_WEB, 1, "GRPC-Web") \ + XXX(GRPC_PTYPE_GRPC_WEB_TEXT, 2, "GRPC-Web-Text") + +typedef VALUE_STRING_ENUM(grpc_protocol_type_vals) grpc_protocol_type_t; +VALUE_STRING_ARRAY(grpc_protocol_type_vals); + +/* grpc frame type (grpc-web extension) */ +#define grpc_frame_type_vals_VALUE_STRING_LIST(XXX) \ + XXX(GRPC_FRAME_TYPE_DATA, 0, "Data") \ + XXX(GRPC_FRAME_TYPE_TRAILER, 1, "Trailer") + +VALUE_STRING_ENUM(grpc_frame_type_vals); +VALUE_STRING_ARRAY(grpc_frame_type_vals); + +/* compressed flag vals */ +#define grpc_compressed_flag_vals_VALUE_STRING_LIST(XXX) \ + XXX(GRPC_NOT_COMPRESSED, 0, "Not Compressed") \ + XXX(GRPC_COMPRESSED, 1, "Compressed") + +VALUE_STRING_ENUM(grpc_compressed_flag_vals); +VALUE_STRING_ARRAY(grpc_compressed_flag_vals); + +/* expert */ +static expert_field ei_grpc_body_decompression_failed = EI_INIT; +static expert_field ei_grpc_body_malformed = EI_INIT; + +/* trees */ +static int ett_grpc = -1; +static int ett_grpc_message = -1; +static int ett_grpc_encoded_entity = -1; + +static dissector_handle_t grpc_handle; +static dissector_handle_t data_text_lines_handle; + +/* the information used during dissecting a grpc message */ +typedef struct { + gboolean is_request; /* is request or response message */ + const gchar* path; /* is http2 ":path" or http request_uri, format: "/" Service-Name "/" {method name} */ + const gchar* content_type; /* is http2 or http content-type, like: application/grpc */ + const gchar* encoding; /* is grpc-encoding header containing compressed method, for example "gzip" */ +} grpc_context_info_t; + +/* GRPC message type dissector table list. +* Dissectors can register themselves in this table as grpc message data dissectors. +* Dissectors registered in this table may use pattern that +* contains content-type,grpc-method-path(http2_path),request/response info, like: +* application/grpc,/helloworld.Greeter/SayHello,request +* or just contains content-type: +* application/grpc +* application/grpc+proto +* application/grpc+json +*/ +static dissector_table_t grpc_message_type_subdissector_table; + +static grpc_protocol_type_t +get_grpc_protocol_type(const gchar* content_type) { + if (content_type != NULL) { + if (g_str_has_prefix(content_type, "application/grpc-web-text")) { + return GRPC_PTYPE_GRPC_WEB_TEXT; + } else if (g_str_has_prefix(content_type, "application/grpc-web")) { + return GRPC_PTYPE_GRPC_WEB; + } + } + return GRPC_PTYPE_GRPC; +} + +/* Try to dissect grpc message according to grpc message info or http2 content_type. */ +static void +dissect_body_data(proto_tree *grpc_tree, packet_info *pinfo, tvbuff_t *tvb, const gint offset, + gint length, gboolean continue_dissect, + guint32 frame_type, grpc_context_info_t *grpc_ctx) +{ + const gchar *http2_content_type = grpc_ctx->content_type; + gchar *grpc_message_info; + tvbuff_t *next_tvb; + int dissected; + proto_tree *parent_tree; + + proto_tree_add_bytes_format_value(grpc_tree, hf_grpc_message_data, tvb, offset, length, NULL, "%u bytes", length); + + if (frame_type == GRPC_FRAME_TYPE_TRAILER) { + call_dissector(data_text_lines_handle, tvb_new_subset_length(tvb, offset, length), pinfo, grpc_tree); + return; + } + + if (!continue_dissect) { + return; /* if uncompress failed, we don't continue dissecting. */ + } + + if (http2_content_type == NULL || grpc_ctx->path == NULL) { + return; /* not continue if there is not enough grpc information */ + } + + next_tvb = tvb_new_subset_length(tvb, offset, length); + + /* Try to detect body as json first. + * Current grpc-java version sends json on grpc with content-type = application/grpc + * insteadof application/grpc+json, so we may detect to dissect message with default + * content-type application/grpc by json dissector insteadof protobuf dissector. + */ + if (grpc_detect_json_automatically && length > 3 + && tvb_get_guint8(next_tvb, 0) == '{') /* start with '{' */ + { + guint8 end_bytes[3]; + tvb_memcpy(next_tvb, end_bytes, length - 3, 3); + if (end_bytes[2] == '}' /* end with '}' */ + || end_bytes[1] == '}' /* or "}\n" */ + || end_bytes[0] == '}') /* or "}\n\r" or " }\r\n" */ + { + /* We just replace content-type with "application/grpc+json" insteadof calling + JSON dissector directly. Because someone may want to use his own dissector to + parse json insteadof default json dissector. */ + http2_content_type = "application/grpc+json"; + } + } + + /* Since message data (like protobuf) may be not a self-describing protocol, we need + * provide grpc service-name, method-name and request or response type to subdissector. + * According to these information, subdissector may find correct message definition + * from IDL file like ".proto". + * + * We define a string format to carry these information. The benefit using string is + * the grpc message information might be used by the other Lua dissector in the future. + * The grpc message information format is: + * http2_content_type "," http2_path "," ("request" / "response") + * Acording to grpc wire format guide, it will be: + * "application/grpc" [("+proto" / "+json" / {custom})] "," "/" service-name "/" method-name "/" "," ("request" / "response") + * For example: + * application/grpc,/helloworld.Greeter/SayHello,request + */ + grpc_message_info = wmem_strconcat(pinfo->pool, http2_content_type, ",", + grpc_ctx->path, ",", (grpc_ctx->is_request ? "request" : "response"), NULL); + + parent_tree = proto_tree_get_parent_tree(grpc_tree); + + /* Protobuf dissector may be implemented that each request or response message + * of a method is defined as an individual dissector, so we try dissect using + * grpc_message_info first. + */ + dissected = dissector_try_string(grpc_message_type_subdissector_table, grpc_message_info, + next_tvb, pinfo, parent_tree, grpc_message_info); + + if (dissected == 0) { + /* not dissected yet, we try common subdissector again. */ + dissector_try_string(grpc_message_type_subdissector_table, http2_content_type, + next_tvb, pinfo, parent_tree, grpc_message_info); + } +} + +static gboolean +can_uncompress_body(const gchar *grpc_encoding) +{ + /* check http2 have a grpc-encoding header appropriate */ + return grpc_decompress_body + && grpc_encoding != NULL + && (strcmp(grpc_encoding, "gzip") == 0 || strcmp(grpc_encoding, "deflate") == 0); +} + +/* Dissect a grpc message. The caller needs to guarantee that the length is equal +to 5 + message_length according to grpc wire format definition. */ +static guint +dissect_grpc_message(tvbuff_t *tvb, guint offset, guint length, packet_info *pinfo, proto_tree *grpc_tree, + grpc_context_info_t* grpc_ctx) +{ + guint32 frame_type, compressed_flag, message_length; + const gchar *compression_method = grpc_ctx->encoding; + + /* GRPC message format: + Delimited-Message -> Compressed-Flag Message-Length Message + Compressed-Flag -> 0 / 1 # encoded as 1 byte unsigned integer + Message-Length -> {length of Message} # encoded as 4 byte unsigned integer + Message -> *{binary octet} (may be protobuf or json) + + Note: GRPC-WEB extend the MSB of Compressed-Flag as frame type (0-data, 1-trailer) + */ + proto_tree_add_item_ret_uint(grpc_tree, hf_grpc_frame_type, tvb, offset, 1, ENC_BIG_ENDIAN, &frame_type); + proto_tree_add_item_ret_uint(grpc_tree, hf_grpc_compressed_flag, tvb, offset, 1, ENC_BIG_ENDIAN, &compressed_flag); + offset += 1; + + if (frame_type == GRPC_FRAME_TYPE_TRAILER) { + proto_item_append_text(proto_tree_get_parent(grpc_tree), " (Trailer)"); + } + + proto_tree_add_item(grpc_tree, hf_grpc_message_length, tvb, offset, 4, ENC_BIG_ENDIAN); + message_length = length - 5; /* should be equal to tvb_get_ntohl(tvb, offset) */ + offset += 4; + + if (message_length == 0) { + return offset; + } + + /* uncompressed message data if compressed_flag is set */ + if (compressed_flag & GRPC_COMPRESSED) { + if (can_uncompress_body(compression_method)) { + proto_item *compressed_proto_item = NULL; + tvbuff_t *uncompressed_tvb = tvb_child_uncompress(tvb, tvb, offset, message_length); + + proto_tree *compressed_entity_tree = proto_tree_add_subtree_format( + grpc_tree, tvb, offset, message_length, ett_grpc_encoded_entity, + &compressed_proto_item, "Message-encoded entity body (%s): %u bytes", + compression_method == NULL ? "unknown" : compression_method, message_length + ); + + if (uncompressed_tvb != NULL) { + guint uncompressed_length = tvb_captured_length(uncompressed_tvb); + add_new_data_source(pinfo, uncompressed_tvb, "Uncompressed entity body"); + proto_item_append_text(compressed_proto_item, " -> %u bytes", uncompressed_length); + dissect_body_data(grpc_tree, pinfo, uncompressed_tvb, 0, uncompressed_length, TRUE, frame_type, grpc_ctx); + } else { + proto_tree_add_expert(compressed_entity_tree, pinfo, &ei_grpc_body_decompression_failed, + tvb, offset, message_length); + dissect_body_data(grpc_tree, pinfo, tvb, offset, message_length, FALSE, frame_type, grpc_ctx); + } + } else { /* compressed flag is set, but we can not uncompressed */ + dissect_body_data(grpc_tree, pinfo, tvb, offset, message_length, FALSE, frame_type, grpc_ctx); + } + } else { + dissect_body_data(grpc_tree, pinfo, tvb, offset, message_length, TRUE, frame_type, grpc_ctx); + } + + return offset + message_length; +} + +static int +dissect_grpc_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, grpc_context_info_t *grpc_ctx) +{ + proto_item *ti; + proto_tree *grpc_tree; + guint32 message_length; + guint offset = 0; + guint tvb_len = tvb_reported_length(tvb); + grpc_protocol_type_t proto_type; + const gchar* proto_name; + + DISSECTOR_ASSERT_HINT(grpc_ctx && grpc_ctx->content_type && grpc_ctx->path, "The content_type and path of grpc context must be set."); + + proto_type = get_grpc_protocol_type(grpc_ctx->content_type); + proto_name = val_to_str_const(proto_type, grpc_protocol_type_vals, "GRPC"); + + if (!grpc_embedded_under_http2 && proto_tree_get_parent_tree(tree)) { + tree = proto_tree_get_parent_tree(tree); + } + + /* http2 had reassembled the http2.data.data, so we need not reassemble again. + reassembled http2.data.data may contain one or more grpc messages. */ + while (offset < tvb_len) + { + if (tvb_len - offset < GRPC_MESSAGE_HEAD_LEN) { + /* need at least 5 bytes for dissecting a grpc message */ + if (pinfo->can_desegment) { + pinfo->desegment_offset = offset; + pinfo->desegment_len = GRPC_MESSAGE_HEAD_LEN - (tvb_len - offset); + return offset; + } + proto_tree_add_expert_format(tree, pinfo, &ei_grpc_body_malformed, tvb, offset, -1, + "GRPC Malformed message data: only %u bytes left, need at least %u bytes.", tvb_len - offset, GRPC_MESSAGE_HEAD_LEN); + break; + } + + message_length = tvb_get_ntohl(tvb, offset + 1); + if (tvb_len - offset < GRPC_MESSAGE_HEAD_LEN + message_length) { + /* remaining bytes are not enough for dissecting the message body */ + if (pinfo->can_desegment) { + pinfo->desegment_offset = offset; + pinfo->desegment_len = GRPC_MESSAGE_HEAD_LEN + message_length - (tvb_len - offset); + return offset; + } + proto_tree_add_expert_format(tree, pinfo, &ei_grpc_body_malformed, tvb, offset, -1, + "GRPC Malformed message data: only %u bytes left, need at least %u bytes.", tvb_len - offset, GRPC_MESSAGE_HEAD_LEN + message_length); + break; + } + /* ready to add information into protocol columns and tree */ + if (offset == 0) { /* change columns only when there is at least one grpc message will be parsed */ + col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_name); + col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", proto_name); + col_set_fence(pinfo->cinfo, COL_PROTOCOL); + } + ti = proto_tree_add_item(tree, proto_grpc, tvb, offset, message_length + GRPC_MESSAGE_HEAD_LEN, ENC_NA); + grpc_tree = proto_item_add_subtree(ti, ett_grpc_message); + proto_item_set_text(ti, "%s Message", proto_name); + + if (grpc_ctx->path) { + proto_item_append_text(ti, ": %s, %s", grpc_ctx->path, (grpc_ctx->is_request ? "Request" : "Response")); + } + + offset = dissect_grpc_message(tvb, offset, GRPC_MESSAGE_HEAD_LEN + message_length, pinfo, grpc_tree, grpc_ctx); + } + + return tvb_captured_length(tvb); +} + +static int +dissect_grpc(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data) +{ + int ret; + http_req_res_t* curr_req_res; + tvbuff_t* real_data_tvb; + grpc_context_info_t grpc_ctx = { 0 }; + media_content_info_t* content_info = (media_content_info_t*)data; + gboolean is_grpc_web_text = g_str_has_prefix(pinfo->match_string, "application/grpc-web-text"); + + if (is_grpc_web_text) { + real_data_tvb = base64_tvb_to_new_tvb(tvb, 0, tvb_reported_length(tvb)); + add_new_data_source(pinfo, real_data_tvb, "Decoded base64 body"); + } else { + real_data_tvb = tvb; + } + + if (proto_is_frame_protocol(pinfo->layers, "http2")) { + grpc_ctx.path = http2_get_header_value(pinfo, HTTP2_HEADER_PATH, FALSE); + grpc_ctx.is_request = (grpc_ctx.path != NULL); + if (grpc_ctx.path == NULL) { + /* this must be response, so we get it from http2 request stream */ + grpc_ctx.path = http2_get_header_value(pinfo, HTTP2_HEADER_PATH, TRUE); + } + grpc_ctx.content_type = http2_get_header_value(pinfo, HTTP2_HEADER_CONTENT_TYPE, FALSE); + grpc_ctx.encoding = http2_get_header_value(pinfo, HTTP2_HEADER_GRPC_ENCODING, FALSE); + } + else if (proto_is_frame_protocol(pinfo->layers, "http")) { + curr_req_res = (http_req_res_t*)p_get_proto_data(wmem_file_scope(), pinfo, proto_http, 0); + DISSECTOR_ASSERT_HINT(curr_req_res && content_info, "Unexpected error: HTTP request/reply or HTTP message info not available."); + grpc_ctx.is_request = (content_info->type == MEDIA_CONTAINER_HTTP_REQUEST); + grpc_ctx.path = curr_req_res->request_uri; + grpc_ctx.content_type = pinfo->match_string; /* only for grpc-web(-text) over http1.1 */ + if (content_info->data) { + grpc_ctx.encoding = (const gchar*)wmem_map_lookup((wmem_map_t *)content_info->data, HTTP2_HEADER_GRPC_ENCODING); + } + } + else { + /* unexpected protocol error */ + DISSECTOR_ASSERT_NOT_REACHED(); + } + + ret = dissect_grpc_common(real_data_tvb, pinfo, tree, &grpc_ctx); + + if (is_grpc_web_text) { + /* convert reassembly the lengths of offset and remaining bytes back to the base64 lengths */ + pinfo->desegment_offset = BASE64_ENCODE_SIZE(pinfo->desegment_offset); + pinfo->desegment_len = BASE64_ENCODE_SIZE(pinfo->desegment_len); + } + + return ret; +} + +void +proto_register_grpc(void) +{ + + static hf_register_info hf[] = { + { &hf_grpc_frame_type, + { "Frame Type", "grpc.frame_type", + FT_UINT8, BASE_DEC, VALS(grpc_frame_type_vals), 0x80, + "The frame type of this grpc message (GRPC-WEB extension)", HFILL } + }, + { &hf_grpc_compressed_flag, + { "Compressed Flag", "grpc.compressed_flag", + FT_UINT8, BASE_DEC, VALS(grpc_compressed_flag_vals), 0x01, + "Compressed-Flag value of 1 indicates that the binary octet sequence of Message is compressed", HFILL } + }, + { &hf_grpc_message_length, + { "Message Length", "grpc.message_length", + FT_UINT32, BASE_DEC, NULL, 0x0, + "The length (32 bits) of message payload (not include itself)", HFILL } + }, + { &hf_grpc_message_data, + { "Message Data", "grpc.message_data", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + } + }; + + static gint *ett[] = { + &ett_grpc, + &ett_grpc_message, + &ett_grpc_encoded_entity + }; + + /* Setup protocol expert items */ + static ei_register_info ei[] = { + { &ei_grpc_body_decompression_failed, + { "grpc.body_decompression_failed", PI_UNDECODED, PI_WARN, + "Body decompression failed", EXPFILL } + }, + { &ei_grpc_body_malformed, + { "grpc.body_malformed", PI_UNDECODED, PI_WARN, + "Malformed message data", EXPFILL } + } + }; + + module_t *grpc_module; + expert_module_t *expert_grpc; + + proto_grpc = proto_register_protocol("GRPC Message", "GRPC", "grpc"); + + proto_register_field_array(proto_grpc, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + grpc_module = prefs_register_protocol(proto_grpc, NULL); + + prefs_register_bool_preference(grpc_module, "detect_json_automatically", + "Always check whether the message is JSON regardless of content-type.", + "Normally application/grpc message is protobuf, " + "but sometime the true message is json. " + "If this option in on, we always check whether the message is JSON " + "(body starts with '{' and ends with '}') regardless of " + "grpc_message_type_subdissector_table settings (which dissect grpc " + "message according to content-type).", + &grpc_detect_json_automatically); + + prefs_register_bool_preference(grpc_module, "embedded_under_http2", + "Embed gRPC messages under HTTP2 (or other) protocol tree items.", + "Embed gRPC messages under HTTP2 (or other) protocol tree items.", + &grpc_embedded_under_http2); + + prefs_register_static_text_preference(grpc_module, "service_definition", + "Please refer to preferences of Protobuf for specifying gRPC Service Definitions (*.proto).", + "Including specifying .proto files search paths, etc."); + + expert_grpc = expert_register_protocol(proto_grpc); + expert_register_field_array(expert_grpc, ei, array_length(ei)); + + grpc_handle = register_dissector("grpc", dissect_grpc, proto_grpc); + + /* + * Dissectors can register themselves in this table as grpc message + * subdissector. Default it support json, protobuf. + */ + grpc_message_type_subdissector_table = + register_dissector_table("grpc_message_type", + "GRPC message type", proto_grpc, FT_STRING, STRING_CASE_SENSITIVE); +} + +void +proto_reg_handoff_grpc(void) +{ + char *content_types[] = { + "application/grpc", + "application/grpc+proto", + "application/grpc+json", + "application/grpc-web", + "application/grpc-web+proto", + "application/grpc-web-text", + "application/grpc-web-text+proto", + NULL /* end flag */ + }; + int i; + + /* register native grpc handler */ + for (i = 0; content_types[i]; i++) { + dissector_add_string("streaming_content_type", content_types[i], grpc_handle); + dissector_add_string("media_type", content_types[i], grpc_handle); + } + + proto_http = proto_get_id_by_filter_name("http"); + data_text_lines_handle = find_dissector_add_dependency("data-text-lines", proto_grpc); +} + +/* +* Editor modelines - https://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: +*/ |