Merge pull request #5643 from mhils/h3-cv

Improve HTTP/3 Frame Contentview
This commit is contained in:
Maximilian Hils 2022-10-13 20:14:48 +02:00 committed by GitHub
commit dbf424169d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 43 deletions

View File

@ -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,

View File

@ -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", "")]

View File

@ -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:

View File

@ -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]))