From 051c90067f43ccb700b1518824421995bedeeb96 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 13 Oct 2022 20:07:20 +0200 Subject: [PATCH 1/2] improve h3 frame contentview --- mitmproxy/contentviews/http3.py | 33 +++++++++++++++++++---- test/mitmproxy/contentviews/test_http3.py | 30 +++++++++++++++------ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/mitmproxy/contentviews/http3.py b/mitmproxy/contentviews/http3.py index bead71f8a..a354a901c 100644 --- a/mitmproxy/contentviews/http3.py +++ b/mitmproxy/contentviews/http3.py @@ -55,10 +55,27 @@ class Frame: ] +@dataclass(frozen=True) +class StreamType: + """Representation of an HTTP/3 stream types.""" + type: int + + def pretty(self): + stream_type = { + 0x00: "Control Stream", + 0x01: "Push Stream", + 0x02: "QPACK Encoder Stream", + 0x03: "QPACK Decoder Stream", + }.get(self.type, f"0x{self.type:x} Stream") + return [ + [("header", stream_type)] + ] + + @dataclass class ConnectionState: message_count: int = 0 - frames: dict[int, list[Frame]] = field(default_factory=dict) + frames: dict[int, list[Frame | StreamType]] = field(default_factory=dict) client_buf: bytearray = field(default_factory=bytearray) server_buf: bytearray = field(default_factory=bytearray) @@ -88,9 +105,15 @@ class ViewHttp3(base.View): buf = state.server_buf buf += message.content - # TODO: It would be much better to know if the stream is unidirectional. - if buf.startswith(b"\x00\x04"): - del buf[:1] + if state.message_count == 0 and flow.metadata["quic_is_unidirectional"]: + h3_buf = Buffer(data=bytes(buf[:8])) + stream_type = h3_buf.pull_uint_var() + consumed = h3_buf.tell() + assert consumed == 1 + del buf[:consumed] + state.frames[0] = [ + StreamType(stream_type) + ] while True: h3_buf = Buffer(data=bytes(buf[:16])) @@ -130,7 +153,7 @@ class ViewHttp3(base.View): return 2 * float(bool(flow and is_h3_alpn(flow.client_conn.alpn))) * float(isinstance(flow, tcp.TCPFlow)) -def fmt_frames(frames: list[Frame]) -> Iterator[base.TViewLine]: +def fmt_frames(frames: list[Frame | StreamType]) -> Iterator[base.TViewLine]: for i, frame in enumerate(frames): if i > 0: yield [("text", "")] diff --git a/test/mitmproxy/contentviews/test_http3.py b/test/mitmproxy/contentviews/test_http3.py index 496e8f926..157ee6914 100644 --- a/test/mitmproxy/contentviews/test_http3.py +++ b/test/mitmproxy/contentviews/test_http3.py @@ -15,14 +15,6 @@ if http3 is None: b"\x01\x1d\x00\x00\xd1\xc1\xd7P\x8a\x08\x9d\\\x0b\x81p\xdcx\x0f\x03_P\x88%\xb6P\xc3\xab\xbc\xda\xe0\xdd", # broken HEADERS b"\x01\x1d\x00\x00\xd1\xc1\xd7P\x8a\x08\x9d\\\x0b\x81p\xdcx\x0f\x03_P\x88%\xb6P\xc3\xab\xff\xff\xff\xff", - # SETTINGS - b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00", - # unknown setting - b"\x00\x04\r\x3f\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00", - # out of bounds - b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x42\x00", - # incomplete - b"\x00\x04\r\x06\xff\xff\xff", # headers + data ( b'\x01@I\x00\x00\xdb_\'\x93I|\xa5\x89\xd3M\x1fj\x12q\xd8\x82\xa6\x0bP\xb0\xd0C\x1b_M\x90\xd0bXt\x1eT\xad\x8f~\xfdp' @@ -38,6 +30,28 @@ def test_view_http3(data): t = tflow.ttcpflow(messages=[ TCPMessage(from_client=len(data) > 16, content=data) ]) + t.metadata["quic_is_unidirectional"] = False + assert (v(b"", flow=t, tcp_message=t.messages[0])) + + +@pytest.mark.parametrize("data", [ + # SETTINGS + b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00", + # unknown setting + b"\x00\x04\r\x3f\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00", + # out of bounds + b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x42\x00", + # incomplete + b"\x00\x04\r\x06\xff\xff\xff", + # QPACK encoder stream + b"\x02", +]) +def test_view_http3_unidirectional(data): + v = full_eval(http3.ViewHttp3()) + t = tflow.ttcpflow(messages=[ + TCPMessage(from_client=len(data) > 16, content=data) + ]) + t.metadata["quic_is_unidirectional"] = True assert (v(b"", flow=t, tcp_message=t.messages[0])) From 7ffdac506b704bf5742a5984ca1fbafb136bb333 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 13 Oct 2022 19:59:55 +0200 Subject: [PATCH 2/2] console: use `get_message_content_view` --- mitmproxy/contentviews/__init__.py | 17 ----------------- mitmproxy/tools/console/flowview.py | 15 ++------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/mitmproxy/contentviews/__init__.py b/mitmproxy/contentviews/__init__.py index 66dc3a98e..86fff00b2 100644 --- a/mitmproxy/contentviews/__init__.py +++ b/mitmproxy/contentviews/__init__.py @@ -159,23 +159,6 @@ def get_message_content_view( return description, lines, error -def get_proto_content_view( - viewname: str, - data: bytes, - flow: Union[TCPFlow, UDPFlow], -): - viewmode = get(viewname) - if not viewmode: - viewmode = get("auto") - - # https://github.com/mitmproxy/mitmproxy/pull/3970#issuecomment-623024447 - assert viewmode - - description, lines, error = get_content_view(viewmode, data, flow=flow) - - return description, lines, error - - def get_content_view( viewmode: View, data: bytes, diff --git a/mitmproxy/tools/console/flowview.py b/mitmproxy/tools/console/flowview.py index 68214e70b..20e969c39 100644 --- a/mitmproxy/tools/console/flowview.py +++ b/mitmproxy/tools/console/flowview.py @@ -255,22 +255,11 @@ class FlowDetails(tabs.Tabs): viewmode = self.master.commands.call("console.flowview.mode") - # Merge adjacent TCP "messages". For detailed explanation of this code block see: - # https://github.com/mitmproxy/mitmproxy/pull/3970/files/469bd32582f764f9a29607efa4f5b04bd87961fb#r418670880 - from_client = None - messages = [] - for message in flow.messages: - if message.from_client is not from_client: - messages.append(message.content) - from_client = message.from_client - else: - messages[-1] += message.content - widget_lines = [] from_client = flow.messages[0].from_client - for m in messages: - _, lines, _ = contentviews.get_proto_content_view(viewmode, m, flow) + for m in flow.messages: + _, lines, _ = contentviews.get_message_content_view(viewmode, m, flow) for line in lines: if from_client: