Skip to content

Commit 480052f

Browse files
mame82mhils
andauthored
Grpc contentview (mitmproxy#4851)
* Partial gRPC contentview prototype, not linted, no tests, not as add-on * Linted (flake8) * Save dev state * Rewrote of protobuf parser, use decoding strategy, reduced rendered data. Parser uses generators * minor cleanup * fix: preferred encoding was provided as function instead of value * flake8: line length * Backlinked message tree objects, temporary debug out * Partial implementation of gRPC definitions. Save state to fix a cras (data invalidate in edit mode) * hack: deal with missing exception handling for generator based content views * gRPC/Protoparser descriptions (with test code) * replaced manual gzip decoding with mitmproxy.net.encoding.decode * Refactored typing imports * Reafctoring * distinguish request vs response definitions, separate view config from parser config * Code cleaning, moved customized protobuf definitions to example addon * final cleanup * changelog * Stubs for tests * Fixed render_riority of addon example * Started adding tests * Work on tests * mypy * Added pseudo encoder to tests, to cover special decodings * Example addon test added * finalized tests, no 100 percent coverage possible, see comments un uncovered code * minor adjustments * fixup tests * Typos Co-authored-by: Maximilian Hils <git@maximilianhils.com>
1 parent aa2ec22 commit 480052f

11 files changed

Lines changed: 1407 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* Windows: Switch to Python's default asyncio event loop, which increases the number of sockets
1010
that can be processed simultaneously (@mhils)
1111
* Add `client_replay_concurrency` option, which allows more than one client replay request to be in-flight at a time. (@rbdixon)
12+
* New content view which handles gRPC/protobuf. Allows to apply custom definitions to visualize different field decodings.
13+
Includes example addon which applies custom definitions for selected gRPC traffic (@mame82)
1214

1315
## 28 September 2021: mitmproxy 7.0.4
1416

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Add a custom version of the gRPC/protobuf content view, which parses
3+
protobuf messages based on a user defined rule set.
4+
5+
"""
6+
from mitmproxy import contentviews
7+
from mitmproxy.contentviews.grpc import ViewGrpcProtobuf, ViewConfig, ProtoParser
8+
9+
config: ViewConfig = ViewConfig()
10+
config.parser_rules = [
11+
# Note:
12+
#
13+
# The first two ParserRules use the same flow filter, although one should reply to request messages and the other to responses.
14+
# Even with '~s' and '~q' filter expressions, the whole flow would be matched (for '~s') or not matched at all (for '~q'), if
15+
# the contentview displays a http.Message belonging to a flow with existing request and response.
16+
# The rules would have to be applied on per-message-basis, instead of per-flow-basis to distinguish request and response (the
17+
# contentview deals with a single message, either request or response, the flow filter with a flow contiaing both).
18+
#
19+
# Thus different ParserRule classes are used to restrict rules to requests or responses were needed:
20+
#
21+
# - ParserRule: applied to requests and responses
22+
# - ParserRuleRequest: applies to requests only
23+
# - ParserRuleResponse: applies to responses only
24+
#
25+
# The actual 'filter' definition in the rule, would still match the whole flow. This means '~u' expressions could
26+
# be used, to match the URL from the request of a flow, while the ParserRuleResponse is only applied to the response.
27+
28+
ProtoParser.ParserRuleRequest(
29+
name = "Geo coordinate lookup request",
30+
# note on flowfilter: for tflow the port gets appended to the URL's host part
31+
filter = "example\\.com.*/ReverseGeocode",
32+
field_definitions=[
33+
ProtoParser.ParserFieldDefinition(tag="1", name="position"),
34+
ProtoParser.ParserFieldDefinition(tag="1.1", name="latitude", intended_decoding=ProtoParser.DecodedTypes.double),
35+
ProtoParser.ParserFieldDefinition(tag="1.2", name="longitude", intended_decoding=ProtoParser.DecodedTypes.double),
36+
ProtoParser.ParserFieldDefinition(tag="3", name="country"),
37+
ProtoParser.ParserFieldDefinition(tag="7", name="app"),
38+
]
39+
),
40+
ProtoParser.ParserRuleResponse(
41+
name = "Geo coordinate lookup response",
42+
# note on flowfilter: for tflow the port gets appended to the URL's host part
43+
filter = "example\\.com.*/ReverseGeocode",
44+
field_definitions=[
45+
ProtoParser.ParserFieldDefinition(tag="1.2", name="address"),
46+
ProtoParser.ParserFieldDefinition(tag="1.3", name="address array element"),
47+
ProtoParser.ParserFieldDefinition(tag="1.3.1", name="unknown bytes", intended_decoding=ProtoParser.DecodedTypes.bytes),
48+
ProtoParser.ParserFieldDefinition(tag="1.3.2", name="element value long"),
49+
ProtoParser.ParserFieldDefinition(tag="1.3.3", name="element value short"),
50+
ProtoParser.ParserFieldDefinition(tag="", tag_prefixes=["1.5.1", "1.5.3", "1.5.4", "1.5.5", "1.5.6"], name="position"),
51+
ProtoParser.ParserFieldDefinition(tag=".1", tag_prefixes=["1.5.1", "1.5.3", "1.5.4", "1.5.5", "1.5.6"], name="latitude", intended_decoding=ProtoParser.DecodedTypes.double), # noqa: E501
52+
ProtoParser.ParserFieldDefinition(tag=".2", tag_prefixes=["1.5.1", "1.5.3", "1.5.4", "1.5.5", "1.5.6"], name="longitude", intended_decoding=ProtoParser.DecodedTypes.double), # noqa: E501
53+
ProtoParser.ParserFieldDefinition(tag="7", name="app"),
54+
]
55+
),
56+
]
57+
58+
59+
class ViewGrpcWithRules(ViewGrpcProtobuf):
60+
name = "customized gRPC/protobuf"
61+
62+
def __init__(self) -> None:
63+
super().__init__(config=config)
64+
65+
def __call__(self, *args, **kwargs) -> contentviews.TViewResult:
66+
heading, lines = super().__call__(*args, **kwargs)
67+
return heading + " (addon with custom rules)", lines
68+
69+
def render_priority(self, *args, **kwargs) -> float:
70+
# increase priority above default gRPC view
71+
s_prio = super().render_priority(*args, **kwargs)
72+
return s_prio + 1 if s_prio > 0 else s_prio
73+
74+
75+
view = ViewGrpcWithRules()
76+
77+
78+
def load(l):
79+
contentviews.add(view)
80+
81+
82+
def done():
83+
contentviews.remove(view)

mitmproxy/contentviews/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from mitmproxy.utils import strutils
2121
from . import (
2222
auto, raw, hex, json, xml_html, wbxml, javascript, css,
23-
urlencoded, multipart, image, query, protobuf, msgpack, graphql
23+
urlencoded, multipart, image, query, protobuf, msgpack, graphql, grpc
2424
)
2525
from .base import View, KEY_MAX, format_text, format_dict, TViewResult
2626
from ..http import HTTPFlow
@@ -187,6 +187,7 @@ def get_content_view(
187187
add(query.ViewQuery())
188188
add(protobuf.ViewProtobuf())
189189
add(msgpack.ViewMsgPack())
190+
add(grpc.ViewGrpcProtobuf())
190191

191192
__all__ = [
192193
"View", "KEY_MAX", "format_text", "format_dict", "TViewResult",

0 commit comments

Comments
 (0)