mirror of
https://github.com/mitmproxy/mitmproxy.git
synced 2025-02-26 00:26:01 +00:00
Add support for raw UDP. (#5414)
This commit is contained in:
parent
3f8da08796
commit
cd4a74fae7
@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased: mitmproxy next
|
||||
|
||||
* Add UDP layer and flow support.
|
||||
([#5414](https://github.com/mitmproxy/mitmproxy/pull/5414), @meitinger)
|
||||
* Setting `connection_strategy` to `lazy` now also disables early
|
||||
upstream connections to fetch TLS certificate details.
|
||||
(@mhils)
|
||||
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
from mitmproxy import hooks, log, addonmanager
|
||||
from mitmproxy.proxy import server_hooks, layer
|
||||
from mitmproxy.proxy.layers import dns, http, modes, tcp, tls, websocket
|
||||
from mitmproxy.proxy.layers import dns, http, modes, tcp, tls, udp, websocket
|
||||
|
||||
known = set()
|
||||
|
||||
@ -128,6 +128,17 @@ with outfile.open("w") as f, contextlib.redirect_stdout(f):
|
||||
],
|
||||
)
|
||||
|
||||
category(
|
||||
"UDP",
|
||||
"",
|
||||
[
|
||||
udp.UdpStartHook,
|
||||
udp.UdpMessageHook,
|
||||
udp.UdpEndHook,
|
||||
udp.UdpErrorHook,
|
||||
],
|
||||
)
|
||||
|
||||
category(
|
||||
"TLS",
|
||||
"",
|
||||
|
@ -36,6 +36,7 @@ modules = [
|
||||
"mitmproxy.proxy.server_hooks",
|
||||
"mitmproxy.tcp",
|
||||
"mitmproxy.tls",
|
||||
"mitmproxy.udp",
|
||||
"mitmproxy.websocket",
|
||||
here / ".." / "src" / "generated" / "events.py",
|
||||
]
|
||||
|
11
docs/src/content/api/mitmproxy.udp.md
Normal file
11
docs/src/content/api/mitmproxy.udp.md
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
---
|
||||
title: "mitmproxy.udp"
|
||||
url: "api/mitmproxy/udp.html"
|
||||
|
||||
menu:
|
||||
addons:
|
||||
parent: 'Event Hooks & API'
|
||||
---
|
||||
|
||||
{{< readfile file="/generated/api/mitmproxy/udp.html" >}}
|
@ -14,6 +14,7 @@ from mitmproxy import flowfilter
|
||||
from mitmproxy import http
|
||||
from mitmproxy.contrib import click as miniclick
|
||||
from mitmproxy.tcp import TCPFlow, TCPMessage
|
||||
from mitmproxy.udp import UDPFlow, UDPMessage
|
||||
from mitmproxy.utils import human
|
||||
from mitmproxy.utils import strutils
|
||||
from mitmproxy.utils import vt_codes
|
||||
@ -106,8 +107,8 @@ class Dumper:
|
||||
|
||||
def _echo_message(
|
||||
self,
|
||||
message: Union[http.Message, TCPMessage, WebSocketMessage],
|
||||
flow: Union[http.HTTPFlow, TCPFlow],
|
||||
message: Union[http.Message, TCPMessage, UDPMessage, WebSocketMessage],
|
||||
flow: Union[http.HTTPFlow, TCPFlow, UDPFlow],
|
||||
):
|
||||
_, lines, error = contentviews.get_message_content_view(
|
||||
ctx.options.dumper_default_contentview, message, flow
|
||||
@ -318,27 +319,40 @@ class Dumper:
|
||||
ret += f" (reason: {websocket.close_reason})"
|
||||
return ret
|
||||
|
||||
def tcp_error(self, f):
|
||||
def _proto_error(self, f):
|
||||
if self.match(f):
|
||||
self.echo(
|
||||
f"Error in TCP connection to {human.format_address(f.server_conn.address)}: {f.error}",
|
||||
f"Error in {f.type.upper()} connection to {human.format_address(f.server_conn.address)}: {f.error}",
|
||||
fg="red",
|
||||
)
|
||||
|
||||
def tcp_message(self, f):
|
||||
def tcp_error(self, f):
|
||||
self._proto_error(f)
|
||||
|
||||
def udp_error(self, f):
|
||||
self._proto_error(f)
|
||||
|
||||
def _proto_message(self, f):
|
||||
if self.match(f):
|
||||
message = f.messages[-1]
|
||||
direction = "->" if message.from_client else "<-"
|
||||
self.echo(
|
||||
"{client} {direction} tcp {direction} {server}".format(
|
||||
"{client} {direction} {type} {direction} {server}".format(
|
||||
client=human.format_address(f.client_conn.peername),
|
||||
server=human.format_address(f.server_conn.address),
|
||||
direction=direction,
|
||||
type=f.type,
|
||||
)
|
||||
)
|
||||
if ctx.options.flow_detail >= 3:
|
||||
self._echo_message(message, f)
|
||||
|
||||
def tcp_message(self, f):
|
||||
self._proto_message(f)
|
||||
|
||||
def udp_message(self, f):
|
||||
self._proto_message(f)
|
||||
|
||||
def _echo_dns_query(self, f: dns.DNSFlow) -> None:
|
||||
client = self._fmt_client(f)
|
||||
opcode = dns.op_codes.to_str(f.request.op_code)
|
||||
|
@ -49,6 +49,9 @@ class Intercept:
|
||||
def tcp_message(self, f):
|
||||
self.process_flow(f)
|
||||
|
||||
def udp_message(self, f):
|
||||
self.process_flow(f)
|
||||
|
||||
def dns_request(self, f):
|
||||
self.process_flow(f)
|
||||
|
||||
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||
from typing import Literal, Optional
|
||||
|
||||
import mitmproxy.types
|
||||
from mitmproxy import command, tcp
|
||||
from mitmproxy import command, tcp, udp
|
||||
from mitmproxy import ctx
|
||||
from mitmproxy import dns
|
||||
from mitmproxy import exceptions
|
||||
@ -148,6 +148,16 @@ class Save:
|
||||
def tcp_error(self, flow: tcp.TCPFlow):
|
||||
self.tcp_end(flow)
|
||||
|
||||
def udp_start(self, flow: udp.UDPFlow):
|
||||
if self.stream:
|
||||
self.active_flows.add(flow)
|
||||
|
||||
def udp_end(self, flow: udp.UDPFlow):
|
||||
self.save_flow(flow)
|
||||
|
||||
def udp_error(self, flow: udp.UDPFlow):
|
||||
self.udp_end(flow)
|
||||
|
||||
def websocket_end(self, flow: http.HTTPFlow):
|
||||
self.save_flow(flow)
|
||||
|
||||
|
@ -27,6 +27,7 @@ from mitmproxy import flowfilter
|
||||
from mitmproxy import http
|
||||
from mitmproxy import io
|
||||
from mitmproxy import tcp
|
||||
from mitmproxy import udp
|
||||
from mitmproxy.utils import human
|
||||
|
||||
|
||||
@ -83,8 +84,8 @@ class OrderRequestMethod(_OrderKey):
|
||||
def generate(self, f: mitmproxy.flow.Flow) -> str:
|
||||
if isinstance(f, http.HTTPFlow):
|
||||
return f.request.method
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
return "TCP"
|
||||
elif isinstance(f, (tcp.TCPFlow, udp.UDPFlow)):
|
||||
return f.type.upper()
|
||||
elif isinstance(f, dns.DNSFlow):
|
||||
return dns.op_codes.to_str(f.request.op_code)
|
||||
else:
|
||||
@ -95,7 +96,7 @@ class OrderRequestURL(_OrderKey):
|
||||
def generate(self, f: mitmproxy.flow.Flow) -> str:
|
||||
if isinstance(f, http.HTTPFlow):
|
||||
return f.request.url
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
elif isinstance(f, (tcp.TCPFlow, udp.UDPFlow)):
|
||||
return human.format_address(f.server_conn.address)
|
||||
elif isinstance(f, dns.DNSFlow):
|
||||
return f.request.questions[0].name if f.request.questions else ""
|
||||
@ -112,7 +113,7 @@ class OrderKeySize(_OrderKey):
|
||||
if f.response and f.response.raw_content:
|
||||
size += len(f.response.raw_content)
|
||||
return size
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
elif isinstance(f, (tcp.TCPFlow, udp.UDPFlow)):
|
||||
size = 0
|
||||
for message in f.messages:
|
||||
size += len(message.content)
|
||||
@ -592,6 +593,18 @@ class View(collections.abc.Sequence):
|
||||
def tcp_end(self, f):
|
||||
self.update([f])
|
||||
|
||||
def udp_start(self, f):
|
||||
self.add([f])
|
||||
|
||||
def udp_message(self, f):
|
||||
self.update([f])
|
||||
|
||||
def udp_error(self, f):
|
||||
self.update([f])
|
||||
|
||||
def udp_end(self, f):
|
||||
self.update([f])
|
||||
|
||||
def dns_request(self, f):
|
||||
self.add([f])
|
||||
|
||||
|
@ -41,6 +41,7 @@ from . import (
|
||||
from .base import View, KEY_MAX, format_text, format_dict, TViewResult
|
||||
from ..http import HTTPFlow
|
||||
from ..tcp import TCPMessage, TCPFlow
|
||||
from ..udp import UDPMessage, UDPFlow
|
||||
from ..websocket import WebSocketMessage
|
||||
|
||||
views: list[View] = []
|
||||
@ -89,8 +90,8 @@ def safe_to_print(lines, encoding="utf8"):
|
||||
|
||||
def get_message_content_view(
|
||||
viewname: str,
|
||||
message: Union[http.Message, TCPMessage, WebSocketMessage],
|
||||
flow: Union[HTTPFlow, TCPFlow],
|
||||
message: Union[http.Message, TCPMessage, UDPMessage, WebSocketMessage],
|
||||
flow: Union[HTTPFlow, TCPFlow, UDPFlow],
|
||||
):
|
||||
"""
|
||||
Like get_content_view, but also handles message encoding.
|
||||
@ -138,10 +139,10 @@ def get_message_content_view(
|
||||
return description, lines, error
|
||||
|
||||
|
||||
def get_tcp_content_view(
|
||||
def get_proto_content_view(
|
||||
viewname: str,
|
||||
data: bytes,
|
||||
flow: TCPFlow,
|
||||
flow: Union[TCPFlow, UDPFlow],
|
||||
):
|
||||
viewmode = get(viewname)
|
||||
if not viewmode:
|
||||
|
@ -5,6 +5,7 @@ from mitmproxy import flow
|
||||
from mitmproxy import hooks
|
||||
from mitmproxy import http
|
||||
from mitmproxy import tcp
|
||||
from mitmproxy import udp
|
||||
from mitmproxy.proxy import layers
|
||||
|
||||
TEventGenerator = Iterator[hooks.Hook]
|
||||
@ -42,6 +43,19 @@ def _iterate_tcp(f: tcp.TCPFlow) -> TEventGenerator:
|
||||
yield layers.tcp.TcpEndHook(f)
|
||||
|
||||
|
||||
def _iterate_udp(f: udp.UDPFlow) -> TEventGenerator:
|
||||
messages = f.messages
|
||||
f.messages = []
|
||||
yield layers.udp.UdpStartHook(f)
|
||||
while messages:
|
||||
f.messages.append(messages.pop(0))
|
||||
yield layers.udp.UdpMessageHook(f)
|
||||
if f.error:
|
||||
yield layers.udp.UdpErrorHook(f)
|
||||
else:
|
||||
yield layers.udp.UdpEndHook(f)
|
||||
|
||||
|
||||
def _iterate_dns(f: dns.DNSFlow) -> TEventGenerator:
|
||||
if f.request:
|
||||
yield layers.dns.DnsRequestHook(f)
|
||||
@ -54,6 +68,7 @@ def _iterate_dns(f: dns.DNSFlow) -> TEventGenerator:
|
||||
_iterate_map: dict[type[flow.Flow], Callable[[Any], TEventGenerator]] = {
|
||||
http.HTTPFlow: _iterate_http,
|
||||
tcp.TCPFlow: _iterate_tcp,
|
||||
udp.UDPFlow: _iterate_udp,
|
||||
dns.DNSFlow: _iterate_dns,
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,7 @@ class Flow(stateobject.StateObject):
|
||||
See also:
|
||||
- mitmproxy.http.HTTPFlow
|
||||
- mitmproxy.tcp.TCPFlow
|
||||
- mitmproxy.udp.UDPFlow
|
||||
"""
|
||||
|
||||
client_conn: connection.Client
|
||||
|
@ -40,7 +40,7 @@ from collections.abc import Sequence
|
||||
from typing import ClassVar, Protocol, Union
|
||||
import pyparsing as pp
|
||||
|
||||
from mitmproxy import dns, flow, http, tcp
|
||||
from mitmproxy import dns, flow, http, tcp, udp
|
||||
|
||||
|
||||
def only(*types):
|
||||
@ -120,6 +120,15 @@ class FTCP(_Action):
|
||||
return True
|
||||
|
||||
|
||||
class FUDP(_Action):
|
||||
code = "udp"
|
||||
help = "Match UDP flows"
|
||||
|
||||
@only(udp.UDPFlow)
|
||||
def __call__(self, f):
|
||||
return True
|
||||
|
||||
|
||||
class FDNS(_Action):
|
||||
code = "dns"
|
||||
help = "Match DNS flows"
|
||||
@ -276,7 +285,7 @@ class FBod(_Rex):
|
||||
help = "Body"
|
||||
flags = re.DOTALL
|
||||
|
||||
@only(http.HTTPFlow, tcp.TCPFlow, dns.DNSFlow)
|
||||
@only(http.HTTPFlow, tcp.TCPFlow, udp.UDPFlow, dns.DNSFlow)
|
||||
def __call__(self, f):
|
||||
if isinstance(f, http.HTTPFlow):
|
||||
if f.request and f.request.raw_content:
|
||||
@ -289,7 +298,7 @@ class FBod(_Rex):
|
||||
for msg in f.websocket.messages:
|
||||
if self.re.search(msg.content):
|
||||
return True
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
elif isinstance(f, (tcp.TCPFlow, udp.UDPFlow)):
|
||||
for msg in f.messages:
|
||||
if self.re.search(msg.content):
|
||||
return True
|
||||
@ -306,7 +315,7 @@ class FBodRequest(_Rex):
|
||||
help = "Request body"
|
||||
flags = re.DOTALL
|
||||
|
||||
@only(http.HTTPFlow, tcp.TCPFlow, dns.DNSFlow)
|
||||
@only(http.HTTPFlow, tcp.TCPFlow, udp.UDPFlow, dns.DNSFlow)
|
||||
def __call__(self, f):
|
||||
if isinstance(f, http.HTTPFlow):
|
||||
if f.request and f.request.raw_content:
|
||||
@ -316,7 +325,7 @@ class FBodRequest(_Rex):
|
||||
for msg in f.websocket.messages:
|
||||
if msg.from_client and self.re.search(msg.content):
|
||||
return True
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
elif isinstance(f, (tcp.TCPFlow, udp.UDPFlow)):
|
||||
for msg in f.messages:
|
||||
if msg.from_client and self.re.search(msg.content):
|
||||
return True
|
||||
@ -330,7 +339,7 @@ class FBodResponse(_Rex):
|
||||
help = "Response body"
|
||||
flags = re.DOTALL
|
||||
|
||||
@only(http.HTTPFlow, tcp.TCPFlow, dns.DNSFlow)
|
||||
@only(http.HTTPFlow, tcp.TCPFlow, udp.UDPFlow, dns.DNSFlow)
|
||||
def __call__(self, f):
|
||||
if isinstance(f, http.HTTPFlow):
|
||||
if f.response and f.response.raw_content:
|
||||
@ -340,7 +349,7 @@ class FBodResponse(_Rex):
|
||||
for msg in f.websocket.messages:
|
||||
if not msg.from_client and self.re.search(msg.content):
|
||||
return True
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
elif isinstance(f, (tcp.TCPFlow, udp.UDPFlow)):
|
||||
for msg in f.messages:
|
||||
if not msg.from_client and self.re.search(msg.content):
|
||||
return True
|
||||
@ -537,6 +546,7 @@ filter_unary: Sequence[type[_Action]] = [
|
||||
FReq,
|
||||
FResp,
|
||||
FTCP,
|
||||
FUDP,
|
||||
FDNS,
|
||||
FWebSocket,
|
||||
FAll,
|
||||
|
@ -304,7 +304,7 @@ class DatagramWriter:
|
||||
self._transport.sendto(data, self._remote_addr)
|
||||
|
||||
def write_eof(self) -> None:
|
||||
raise NotImplementedError("UDP does not support half-closing.")
|
||||
raise OSError("UDP does not support half-closing.")
|
||||
|
||||
def get_extra_info(self, name: str, default: Any = None) -> Any:
|
||||
if name == "peername":
|
||||
|
@ -2,6 +2,7 @@ from . import modes
|
||||
from .dns import DNSLayer
|
||||
from .http import HttpLayer
|
||||
from .tcp import TCPLayer
|
||||
from .udp import UDPLayer
|
||||
from .tls import ClientTLSLayer, ServerTLSLayer
|
||||
from .websocket import WebsocketLayer
|
||||
|
||||
@ -10,6 +11,7 @@ __all__ = [
|
||||
"DNSLayer",
|
||||
"HttpLayer",
|
||||
"TCPLayer",
|
||||
"UDPLayer",
|
||||
"ClientTLSLayer",
|
||||
"ServerTLSLayer",
|
||||
"WebsocketLayer",
|
||||
|
133
mitmproxy/proxy/layers/udp.py
Normal file
133
mitmproxy/proxy/layers/udp.py
Normal file
@ -0,0 +1,133 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from mitmproxy import flow, udp
|
||||
from mitmproxy.proxy import commands, events, layer
|
||||
from mitmproxy.proxy.commands import StartHook
|
||||
from mitmproxy.connection import Connection
|
||||
from mitmproxy.proxy.context import Context
|
||||
from mitmproxy.proxy.events import MessageInjected
|
||||
from mitmproxy.proxy.utils import expect
|
||||
|
||||
|
||||
@dataclass
|
||||
class UdpStartHook(StartHook):
|
||||
"""
|
||||
A UDP connection has started.
|
||||
"""
|
||||
|
||||
flow: udp.UDPFlow
|
||||
|
||||
|
||||
@dataclass
|
||||
class UdpMessageHook(StartHook):
|
||||
"""
|
||||
A UDP connection has received a message. The most recent message
|
||||
will be flow.messages[-1]. The message is user-modifiable.
|
||||
"""
|
||||
|
||||
flow: udp.UDPFlow
|
||||
|
||||
|
||||
@dataclass
|
||||
class UdpEndHook(StartHook):
|
||||
"""
|
||||
A UDP connection has ended.
|
||||
"""
|
||||
|
||||
flow: udp.UDPFlow
|
||||
|
||||
|
||||
@dataclass
|
||||
class UdpErrorHook(StartHook):
|
||||
"""
|
||||
A UDP error has occurred.
|
||||
|
||||
Every UDP flow will receive either a udp_error or a udp_end event, but not both.
|
||||
"""
|
||||
|
||||
flow: udp.UDPFlow
|
||||
|
||||
|
||||
class UdpMessageInjected(MessageInjected[udp.UDPMessage]):
|
||||
"""
|
||||
The user has injected a custom UDP message.
|
||||
"""
|
||||
|
||||
|
||||
class UDPLayer(layer.Layer):
|
||||
"""
|
||||
Simple UDP layer that just relays messages right now.
|
||||
"""
|
||||
|
||||
flow: Optional[udp.UDPFlow]
|
||||
|
||||
def __init__(self, context: Context, ignore: bool = False):
|
||||
super().__init__(context)
|
||||
if ignore:
|
||||
self.flow = None
|
||||
else:
|
||||
self.flow = udp.UDPFlow(self.context.client, self.context.server, True)
|
||||
|
||||
@expect(events.Start)
|
||||
def start(self, _) -> layer.CommandGenerator[None]:
|
||||
if self.flow:
|
||||
yield UdpStartHook(self.flow)
|
||||
|
||||
if self.context.server.timestamp_start is None:
|
||||
err = yield commands.OpenConnection(self.context.server)
|
||||
if err:
|
||||
if self.flow:
|
||||
self.flow.error = flow.Error(str(err))
|
||||
yield UdpErrorHook(self.flow)
|
||||
yield commands.CloseConnection(self.context.client)
|
||||
self._handle_event = self.done
|
||||
return
|
||||
self._handle_event = self.relay_messages
|
||||
|
||||
_handle_event = start
|
||||
|
||||
@expect(events.DataReceived, events.ConnectionClosed, UdpMessageInjected)
|
||||
def relay_messages(self, event: events.Event) -> layer.CommandGenerator[None]:
|
||||
|
||||
if isinstance(event, UdpMessageInjected):
|
||||
# we just spoof that we received data here and then process that regularly.
|
||||
event = events.DataReceived(
|
||||
self.context.client
|
||||
if event.message.from_client
|
||||
else self.context.server,
|
||||
event.message.content,
|
||||
)
|
||||
|
||||
assert isinstance(event, events.ConnectionEvent)
|
||||
|
||||
from_client = event.connection == self.context.client
|
||||
send_to: Connection
|
||||
if from_client:
|
||||
send_to = self.context.server
|
||||
else:
|
||||
send_to = self.context.client
|
||||
|
||||
if isinstance(event, events.DataReceived):
|
||||
if self.flow:
|
||||
udp_message = udp.UDPMessage(from_client, event.data)
|
||||
self.flow.messages.append(udp_message)
|
||||
yield UdpMessageHook(self.flow)
|
||||
yield commands.SendData(send_to, udp_message.content)
|
||||
else:
|
||||
yield commands.SendData(send_to, event.data)
|
||||
|
||||
elif isinstance(event, events.ConnectionClosed):
|
||||
if send_to.connected:
|
||||
yield commands.CloseConnection(send_to)
|
||||
else:
|
||||
self._handle_event = self.done
|
||||
if self.flow:
|
||||
yield UdpEndHook(self.flow)
|
||||
self.flow.live = False
|
||||
else:
|
||||
raise AssertionError(f"Unexpected event: {event}")
|
||||
|
||||
@expect(events.DataReceived, events.ConnectionClosed, UdpMessageInjected)
|
||||
def done(self, _) -> layer.CommandGenerator[None]:
|
||||
yield from ()
|
@ -6,6 +6,7 @@ from mitmproxy import dns
|
||||
from mitmproxy import flow
|
||||
from mitmproxy import http
|
||||
from mitmproxy import tcp
|
||||
from mitmproxy import udp
|
||||
from mitmproxy import websocket
|
||||
from mitmproxy.proxy.mode_specs import ProxyMode
|
||||
from mitmproxy.test.tutils import tdnsreq, tdnsresp
|
||||
@ -36,6 +37,29 @@ def ttcpflow(
|
||||
return f
|
||||
|
||||
|
||||
def tudpflow(
|
||||
client_conn=True, server_conn=True, messages=True, err=None
|
||||
) -> udp.UDPFlow:
|
||||
if client_conn is True:
|
||||
client_conn = tclient_conn()
|
||||
if server_conn is True:
|
||||
server_conn = tserver_conn()
|
||||
if messages is True:
|
||||
messages = [
|
||||
udp.UDPMessage(True, b"hello", 946681204.2),
|
||||
udp.UDPMessage(False, b"it's me", 946681204.5),
|
||||
]
|
||||
if err is True:
|
||||
err = terr()
|
||||
|
||||
f = udp.UDPFlow(client_conn, server_conn)
|
||||
f.timestamp_created = client_conn.timestamp_start
|
||||
f.messages = messages
|
||||
f.error = err
|
||||
f.live = True
|
||||
return f
|
||||
|
||||
|
||||
def twebsocketflow(
|
||||
messages=True, err=None, close_code=None, close_reason=""
|
||||
) -> http.HTTPFlow:
|
||||
@ -272,6 +296,8 @@ def tflows() -> list[flow.Flow]:
|
||||
tflow(ws=True),
|
||||
ttcpflow(),
|
||||
ttcpflow(err=True),
|
||||
tudpflow(),
|
||||
tudpflow(err=True),
|
||||
tdnsflow(resp=True),
|
||||
tdnsflow(err=True),
|
||||
]
|
||||
|
@ -14,6 +14,7 @@ from mitmproxy import flow
|
||||
from mitmproxy.http import HTTPFlow
|
||||
from mitmproxy.utils import human, emoji
|
||||
from mitmproxy.tcp import TCPFlow
|
||||
from mitmproxy.udp import UDPFlow
|
||||
from mitmproxy import dns
|
||||
from mitmproxy.dns import DNSFlow
|
||||
|
||||
@ -116,6 +117,7 @@ SCHEME_STYLES = {
|
||||
"ws": "scheme_ws",
|
||||
"wss": "scheme_wss",
|
||||
"tcp": "scheme_tcp",
|
||||
"udp": "scheme_udp",
|
||||
"dns": "scheme_dns",
|
||||
}
|
||||
HTTP_REQUEST_METHOD_STYLES = {
|
||||
@ -604,12 +606,13 @@ def format_http_flow_table(
|
||||
|
||||
|
||||
@lru_cache(maxsize=800)
|
||||
def format_tcp_flow(
|
||||
def format_message_flow(
|
||||
*,
|
||||
render_mode: RenderMode,
|
||||
focused: bool,
|
||||
timestamp_start: float,
|
||||
marked: str,
|
||||
protocol: str,
|
||||
client_address,
|
||||
server_address,
|
||||
total_size: int,
|
||||
@ -633,9 +636,9 @@ def format_tcp_flow(
|
||||
items.append(fcol(" ", "focus"))
|
||||
|
||||
if render_mode is RenderMode.TABLE:
|
||||
items.append(fcol("TCP ", SCHEME_STYLES["tcp"]))
|
||||
items.append(fcol(fixlen(protocol.upper(), 5), SCHEME_STYLES[protocol]))
|
||||
else:
|
||||
items.append(fcol("TCP", SCHEME_STYLES["tcp"]))
|
||||
items.append(fcol(protocol.upper(), SCHEME_STYLES[protocol]))
|
||||
|
||||
items.append(("weight", 1.0, truncated_plain(conn, "text", "left")))
|
||||
if error_message:
|
||||
@ -752,7 +755,7 @@ def format_flow(
|
||||
else:
|
||||
error_message = None
|
||||
|
||||
if isinstance(f, TCPFlow):
|
||||
if isinstance(f, (TCPFlow, UDPFlow)):
|
||||
total_size = 0
|
||||
for message in f.messages:
|
||||
total_size += len(message.content)
|
||||
@ -760,11 +763,12 @@ def format_flow(
|
||||
duration = f.messages[-1].timestamp - f.client_conn.timestamp_start
|
||||
else:
|
||||
duration = None
|
||||
return format_tcp_flow(
|
||||
return format_message_flow(
|
||||
render_mode=render_mode,
|
||||
focused=focused,
|
||||
timestamp_start=f.client_conn.timestamp_start,
|
||||
marked=f.marked,
|
||||
protocol=f.type,
|
||||
client_address=f.client_conn.peername,
|
||||
server_address=f.server_conn.address,
|
||||
total_size=total_size,
|
||||
|
@ -11,6 +11,7 @@ from mitmproxy import flow
|
||||
from mitmproxy import http
|
||||
from mitmproxy import log
|
||||
from mitmproxy import tcp
|
||||
from mitmproxy import udp
|
||||
from mitmproxy.tools.console import keymap
|
||||
from mitmproxy.tools.console import overlay
|
||||
from mitmproxy.tools.console import signals
|
||||
@ -308,7 +309,7 @@ class ConsoleAddon:
|
||||
@command.command("console.view.flow")
|
||||
def view_flow(self, flow: flow.Flow) -> None:
|
||||
"""View a flow."""
|
||||
if isinstance(flow, (http.HTTPFlow, tcp.TCPFlow, dns.DNSFlow)):
|
||||
if isinstance(flow, (http.HTTPFlow, tcp.TCPFlow, udp.UDPFlow, dns.DNSFlow)):
|
||||
self.master.switch_view("flowview")
|
||||
else:
|
||||
ctx.log.warn(f"No detail view for {type(flow).__name__}.")
|
||||
@ -363,6 +364,8 @@ class ConsoleAddon:
|
||||
|
||||
if isinstance(flow, tcp.TCPFlow):
|
||||
focus_options = ["tcp-message"]
|
||||
elif isinstance(flow, udp.UDPFlow):
|
||||
focus_options = ["udp-message"]
|
||||
elif isinstance(flow, http.HTTPFlow):
|
||||
focus_options = [
|
||||
"cookies",
|
||||
@ -447,7 +450,7 @@ class ConsoleAddon:
|
||||
self.master.commands.call_strings(
|
||||
"console.command", ["flow.set", "@focus", flow_part]
|
||||
)
|
||||
elif flow_part == "tcp-message":
|
||||
elif flow_part in ["tcp-message", "udp-message"]:
|
||||
message = flow.messages[-1]
|
||||
c = self.master.spawn_editor(message.content or b"")
|
||||
message.content = c.rstrip(b"\n")
|
||||
|
@ -12,6 +12,7 @@ from mitmproxy import ctx
|
||||
from mitmproxy import dns
|
||||
from mitmproxy import http
|
||||
from mitmproxy import tcp
|
||||
from mitmproxy import udp
|
||||
from mitmproxy.tools.console import common
|
||||
from mitmproxy.tools.console import flowdetailview
|
||||
from mitmproxy.tools.console import layoutwidget
|
||||
@ -87,7 +88,12 @@ class FlowDetails(tabs.Tabs):
|
||||
]
|
||||
elif isinstance(f, tcp.TCPFlow):
|
||||
self.tabs = [
|
||||
(self.tab_tcp_stream, self.view_tcp_stream),
|
||||
(self.tab_tcp_stream, self.view_message_stream),
|
||||
(self.tab_details, self.view_details),
|
||||
]
|
||||
elif isinstance(f, udp.UDPFlow):
|
||||
self.tabs = [
|
||||
(self.tab_udp_stream, self.view_message_stream),
|
||||
(self.tab_details, self.view_details),
|
||||
]
|
||||
elif isinstance(f, dns.DNSFlow):
|
||||
@ -135,6 +141,9 @@ class FlowDetails(tabs.Tabs):
|
||||
def tab_tcp_stream(self):
|
||||
return "TCP Stream"
|
||||
|
||||
def tab_udp_stream(self):
|
||||
return "UDP Stream"
|
||||
|
||||
def tab_websocket_messages(self):
|
||||
return "WebSocket Messages"
|
||||
|
||||
@ -235,9 +244,9 @@ class FlowDetails(tabs.Tabs):
|
||||
|
||||
return searchable.Searchable(widget_lines)
|
||||
|
||||
def view_tcp_stream(self) -> urwid.Widget:
|
||||
def view_message_stream(self) -> urwid.Widget:
|
||||
flow = self.flow
|
||||
assert isinstance(flow, tcp.TCPFlow)
|
||||
assert isinstance(flow, (tcp.TCPFlow, udp.UDPFlow))
|
||||
|
||||
if not flow.messages:
|
||||
return searchable.Searchable([urwid.Text(("highlight", "No messages."))])
|
||||
@ -259,7 +268,7 @@ class FlowDetails(tabs.Tabs):
|
||||
|
||||
from_client = flow.messages[0].from_client
|
||||
for m in messages:
|
||||
_, lines, _ = contentviews.get_tcp_content_view(viewmode, m, flow)
|
||||
_, lines, _ = contentviews.get_proto_content_view(viewmode, m, flow)
|
||||
|
||||
for line in lines:
|
||||
if from_client:
|
||||
|
@ -37,6 +37,7 @@ class Palette:
|
||||
"scheme_ws",
|
||||
"scheme_wss",
|
||||
"scheme_tcp",
|
||||
"scheme_udp",
|
||||
"scheme_dns",
|
||||
"scheme_other",
|
||||
"url_punctuation",
|
||||
@ -174,6 +175,7 @@ class LowDark(Palette):
|
||||
scheme_ws=("brown", "default"),
|
||||
scheme_wss=("dark magenta", "default"),
|
||||
scheme_tcp=("dark magenta", "default"),
|
||||
scheme_udp=("dark magenta", "default"),
|
||||
scheme_dns=("dark blue", "default"),
|
||||
scheme_other=("dark magenta", "default"),
|
||||
url_punctuation=("light gray", "default"),
|
||||
@ -272,6 +274,7 @@ class LowLight(Palette):
|
||||
scheme_ws=("brown", "default"),
|
||||
scheme_wss=("light magenta", "default"),
|
||||
scheme_tcp=("light magenta", "default"),
|
||||
scheme_udp=("light magenta", "default"),
|
||||
scheme_dns=("light blue", "default"),
|
||||
scheme_other=("light magenta", "default"),
|
||||
url_punctuation=("dark gray", "default"),
|
||||
@ -391,6 +394,7 @@ class SolarizedLight(LowLight):
|
||||
scheme_ws=(sol_orange, "default"),
|
||||
scheme_wss=("light magenta", "default"),
|
||||
scheme_tcp=("light magenta", "default"),
|
||||
scheme_udp=("light magenta", "default"),
|
||||
scheme_dns=("light blue", "default"),
|
||||
scheme_other=("light magenta", "default"),
|
||||
url_punctuation=("dark gray", "default"),
|
||||
|
@ -25,6 +25,7 @@ from mitmproxy import version
|
||||
from mitmproxy.dns import DNSFlow
|
||||
from mitmproxy.http import HTTPFlow
|
||||
from mitmproxy.tcp import TCPFlow, TCPMessage
|
||||
from mitmproxy.udp import UDPFlow, UDPMessage
|
||||
from mitmproxy.utils.emoji import emoji
|
||||
from mitmproxy.utils.strutils import always_str
|
||||
from mitmproxy.websocket import WebSocketMessage
|
||||
@ -162,7 +163,7 @@ def flow_to_json(flow: mitmproxy.flow.Flow) -> dict:
|
||||
"close_reason": flow.websocket.close_reason,
|
||||
"timestamp_end": flow.websocket.timestamp_end,
|
||||
}
|
||||
elif isinstance(flow, TCPFlow):
|
||||
elif isinstance(flow, (TCPFlow, UDPFlow)):
|
||||
f["messages_meta"] = {
|
||||
"contentLength": sum(len(x.content) for x in flow.messages),
|
||||
"count": len(flow.messages),
|
||||
@ -477,8 +478,8 @@ class FlowContentView(RequestHandler):
|
||||
def message_to_json(
|
||||
self,
|
||||
viewname: str,
|
||||
message: Union[http.Message, TCPMessage, WebSocketMessage],
|
||||
flow: Union[HTTPFlow, TCPFlow],
|
||||
message: Union[http.Message, TCPMessage, UDPMessage, WebSocketMessage],
|
||||
flow: Union[HTTPFlow, TCPFlow, UDPFlow],
|
||||
max_lines: Optional[int] = None,
|
||||
):
|
||||
description, lines, error = contentviews.get_message_content_view(
|
||||
@ -496,7 +497,7 @@ class FlowContentView(RequestHandler):
|
||||
|
||||
def get(self, flow_id, message, content_view):
|
||||
flow = self.flow
|
||||
assert isinstance(flow, (HTTPFlow, TCPFlow))
|
||||
assert isinstance(flow, (HTTPFlow, TCPFlow, UDPFlow))
|
||||
|
||||
if self.request.arguments.get("lines"):
|
||||
max_lines = int(self.request.arguments["lines"][0])
|
||||
@ -506,7 +507,7 @@ class FlowContentView(RequestHandler):
|
||||
if message == "messages":
|
||||
if isinstance(flow, HTTPFlow) and flow.websocket:
|
||||
messages = flow.websocket.messages
|
||||
elif isinstance(flow, TCPFlow):
|
||||
elif isinstance(flow, (TCPFlow, UDPFlow)):
|
||||
messages = flow.messages
|
||||
else:
|
||||
raise APIError(400, f"This flow has no messages.")
|
||||
|
2
mitmproxy/tools/web/static/app.css
vendored
2
mitmproxy/tools/web/static/app.css
vendored
File diff suppressed because one or more lines are too long
80
mitmproxy/tools/web/static/app.js
vendored
80
mitmproxy/tools/web/static/app.js
vendored
File diff suppressed because one or more lines are too long
BIN
mitmproxy/tools/web/static/images/resourceUdpIcon.png
vendored
Normal file
BIN
mitmproxy/tools/web/static/images/resourceUdpIcon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 936 B |
64
mitmproxy/udp.py
Normal file
64
mitmproxy/udp.py
Normal file
@ -0,0 +1,64 @@
|
||||
import time
|
||||
|
||||
from mitmproxy import connection, flow
|
||||
from mitmproxy.coretypes import serializable
|
||||
|
||||
|
||||
class UDPMessage(serializable.Serializable):
|
||||
"""
|
||||
An individual UDP datagram.
|
||||
"""
|
||||
|
||||
def __init__(self, from_client, content, timestamp=None):
|
||||
self.from_client = from_client
|
||||
self.content = content
|
||||
self.timestamp = timestamp or time.time()
|
||||
|
||||
@classmethod
|
||||
def from_state(cls, state):
|
||||
return cls(*state)
|
||||
|
||||
def get_state(self):
|
||||
return self.from_client, self.content, self.timestamp
|
||||
|
||||
def set_state(self, state):
|
||||
self.from_client, self.content, self.timestamp = state
|
||||
|
||||
def __repr__(self):
|
||||
return "{direction} {content}".format(
|
||||
direction="->" if self.from_client else "<-", content=repr(self.content)
|
||||
)
|
||||
|
||||
|
||||
class UDPFlow(flow.Flow):
|
||||
"""
|
||||
A UDPFlow is a representation of a UDP session.
|
||||
"""
|
||||
|
||||
messages: list[UDPMessage]
|
||||
"""
|
||||
The messages transmitted over this connection.
|
||||
|
||||
The latest message can be accessed as `flow.messages[-1]` in event hooks.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client_conn: connection.Client,
|
||||
server_conn: connection.Server,
|
||||
live: bool = False,
|
||||
):
|
||||
super().__init__(client_conn, server_conn, live)
|
||||
self.messages = []
|
||||
|
||||
_stateobject_attributes = flow.Flow._stateobject_attributes.copy()
|
||||
_stateobject_attributes["messages"] = list[UDPMessage]
|
||||
|
||||
def __repr__(self):
|
||||
return f"<UDPFlow ({len(self.messages)} messages)>"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"UDPFlow",
|
||||
"UDPMessage",
|
||||
]
|
@ -39,6 +39,13 @@ def tcp(level):
|
||||
show(level, [f1])
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--level", default=1, help="Detail level")
|
||||
def udp(level):
|
||||
f1 = tflow.tudpflow()
|
||||
show(level, [f1])
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--level", default=1, help="Detail level")
|
||||
def large(level):
|
||||
|
@ -131,9 +131,9 @@ def test_check():
|
||||
f.request.raw_content = None
|
||||
assert "missing content" in cp.check(f)
|
||||
|
||||
f = tflow.ttcpflow()
|
||||
f.live = False
|
||||
assert "Can only replay HTTP" in cp.check(f)
|
||||
for f in (tflow.ttcpflow(), tflow.tudpflow()):
|
||||
f.live = False
|
||||
assert "Can only replay HTTP" in cp.check(f)
|
||||
|
||||
|
||||
async def test_start_stop(tdata):
|
||||
|
@ -171,8 +171,9 @@ def test_cut():
|
||||
assert c.cut(tflows, ["response.reason"]) == [[""]]
|
||||
assert c.cut(tflows, ["response.header[key]"]) == [[""]]
|
||||
|
||||
c = cut.Cut()
|
||||
with taddons.context():
|
||||
tflows = [tflow.ttcpflow()]
|
||||
assert c.cut(tflows, ["request.method"]) == [[""]]
|
||||
assert c.cut(tflows, ["response.status"]) == [[""]]
|
||||
for f in (tflow.ttcpflow(), tflow.tudpflow()):
|
||||
c = cut.Cut()
|
||||
with taddons.context():
|
||||
tflows = [f]
|
||||
assert c.cut(tflows, ["request.method"]) == [[""]]
|
||||
assert c.cut(tflows, ["response.status"]) == [[""]]
|
||||
|
@ -199,6 +199,21 @@ def test_tcp():
|
||||
assert "Error in TCP" in sio.getvalue()
|
||||
|
||||
|
||||
def test_udp():
|
||||
sio = io.StringIO()
|
||||
d = dumper.Dumper(sio)
|
||||
with taddons.context(d) as ctx:
|
||||
ctx.configure(d, flow_detail=3, showhost=True)
|
||||
f = tflow.tudpflow()
|
||||
d.udp_message(f)
|
||||
assert "it's me" in sio.getvalue()
|
||||
sio.truncate(0)
|
||||
|
||||
f = tflow.tudpflow(client_conn=True, err=True)
|
||||
d.udp_error(f)
|
||||
assert "Error in UDP" in sio.getvalue()
|
||||
|
||||
|
||||
def test_dns():
|
||||
sio = io.StringIO()
|
||||
d = dumper.Dumper(sio)
|
||||
|
@ -53,6 +53,11 @@ def tcp_flow():
|
||||
return tflow.ttcpflow()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def udp_flow():
|
||||
return tflow.tudpflow()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def export_curl():
|
||||
e = export.Export()
|
||||
@ -88,6 +93,10 @@ class TestExportCurlCommand:
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export_curl(tcp_flow)
|
||||
|
||||
def test_udp(self, export_curl, udp_flow):
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export_curl(udp_flow)
|
||||
|
||||
def test_escape_single_quotes_in_body(self, export_curl):
|
||||
request = tflow.tflow(
|
||||
req=tutils.treq(method=b"POST", headers=(), content=b"'&#")
|
||||
@ -153,6 +162,10 @@ class TestExportHttpieCommand:
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.httpie_command(tcp_flow)
|
||||
|
||||
def test_udp(self, udp_flow):
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.httpie_command(udp_flow)
|
||||
|
||||
def test_escape_single_quotes_in_body(self):
|
||||
request = tflow.tflow(
|
||||
req=tutils.treq(method=b"POST", headers=(), content=b"'&#")
|
||||
@ -197,6 +210,13 @@ class TestRaw:
|
||||
):
|
||||
export.raw(tcp_flow)
|
||||
|
||||
def test_udp(self, udp_flow):
|
||||
with pytest.raises(
|
||||
exceptions.CommandError,
|
||||
match="Can't export flow with no request or response",
|
||||
):
|
||||
export.raw(udp_flow)
|
||||
|
||||
|
||||
class TestRawRequest:
|
||||
def test_get(self, get_request):
|
||||
@ -212,6 +232,10 @@ class TestRawRequest:
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.raw_request(tcp_flow)
|
||||
|
||||
def test_udp(self, udp_flow):
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.raw_request(udp_flow)
|
||||
|
||||
|
||||
class TestRawResponse:
|
||||
def test_get(self, get_response):
|
||||
@ -226,6 +250,10 @@ class TestRawResponse:
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.raw_response(tcp_flow)
|
||||
|
||||
def test_udp(self, udp_flow):
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.raw_response(udp_flow)
|
||||
|
||||
|
||||
def qr(f):
|
||||
with open(f, "rb") as fp:
|
||||
|
@ -75,3 +75,17 @@ async def test_tcp():
|
||||
f = tflow.ttcpflow()
|
||||
await tctx.cycle(r, f)
|
||||
assert not f.intercepted
|
||||
|
||||
|
||||
async def test_udp():
|
||||
r = intercept.Intercept()
|
||||
with taddons.context(r) as tctx:
|
||||
tctx.configure(r, intercept="~udp")
|
||||
f = tflow.tudpflow()
|
||||
await tctx.cycle(r, f)
|
||||
assert f.intercepted
|
||||
|
||||
tctx.configure(r, intercept_active=False)
|
||||
f = tflow.tudpflow()
|
||||
await tctx.cycle(r, f)
|
||||
assert not f.intercepted
|
||||
|
@ -47,6 +47,24 @@ def test_tcp(tmp_path):
|
||||
assert len(rd(p)) == 2
|
||||
|
||||
|
||||
def test_udp(tmp_path):
|
||||
sa = save.Save()
|
||||
with taddons.context(sa) as tctx:
|
||||
p = str(tmp_path / "foo")
|
||||
tctx.configure(sa, save_stream_file=p)
|
||||
|
||||
tt = tflow.tudpflow()
|
||||
sa.udp_start(tt)
|
||||
sa.udp_end(tt)
|
||||
|
||||
tt = tflow.tudpflow()
|
||||
sa.udp_start(tt)
|
||||
sa.udp_error(tt)
|
||||
|
||||
tctx.configure(sa, save_stream_file=None)
|
||||
assert len(rd(p)) == 2
|
||||
|
||||
|
||||
def test_dns(tmp_path):
|
||||
sa = save.Save()
|
||||
with taddons.context(sa) as tctx:
|
||||
|
@ -74,15 +74,13 @@ def test_order_generators_dns():
|
||||
assert sz.generate(tf) == 0
|
||||
|
||||
|
||||
def test_order_generators_tcp():
|
||||
def order_generators_proto(tf, name):
|
||||
v = view.View()
|
||||
tf = tflow.ttcpflow()
|
||||
|
||||
rs = view.OrderRequestStart(v)
|
||||
assert rs.generate(tf) == 946681200
|
||||
|
||||
rm = view.OrderRequestMethod(v)
|
||||
assert rm.generate(tf) == "TCP"
|
||||
assert rm.generate(tf) == name
|
||||
|
||||
ru = view.OrderRequestURL(v)
|
||||
assert ru.generate(tf) == "address:22"
|
||||
@ -91,6 +89,14 @@ def test_order_generators_tcp():
|
||||
assert sz.generate(tf) == sum(len(m.content) for m in tf.messages)
|
||||
|
||||
|
||||
def test_order_generators_tcp():
|
||||
order_generators_proto(tflow.ttcpflow(), "TCP")
|
||||
|
||||
|
||||
def test_order_generators_udp():
|
||||
order_generators_proto(tflow.tudpflow(), "UDP")
|
||||
|
||||
|
||||
def test_simple():
|
||||
v = view.View()
|
||||
f = tft(start=1)
|
||||
@ -158,6 +164,21 @@ def test_simple_tcp():
|
||||
assert list(v) == [f]
|
||||
|
||||
|
||||
def test_simple_udp():
|
||||
v = view.View()
|
||||
f = tflow.tudpflow()
|
||||
assert v.store_count() == 0
|
||||
v.udp_start(f)
|
||||
assert list(v) == [f]
|
||||
|
||||
# These all just call update
|
||||
v.udp_start(f)
|
||||
v.udp_message(f)
|
||||
v.udp_error(f)
|
||||
v.udp_end(f)
|
||||
assert list(v) == [f]
|
||||
|
||||
|
||||
def test_simple_dns():
|
||||
v = view.View()
|
||||
f = tflow.tdnsflow(resp=True, err=True)
|
||||
|
124
test/mitmproxy/proxy/layers/test_udp.py
Normal file
124
test/mitmproxy/proxy/layers/test_udp.py
Normal file
@ -0,0 +1,124 @@
|
||||
import pytest
|
||||
|
||||
from mitmproxy.proxy.commands import CloseConnection, OpenConnection, SendData
|
||||
from mitmproxy.proxy.events import ConnectionClosed, DataReceived
|
||||
from mitmproxy.proxy.layers import udp
|
||||
from mitmproxy.proxy.layers.udp import UdpMessageInjected
|
||||
from mitmproxy.udp import UDPFlow, UDPMessage
|
||||
from ..tutils import Placeholder, Playbook, reply
|
||||
|
||||
|
||||
def test_open_connection(tctx):
|
||||
"""
|
||||
If there is no server connection yet, establish one,
|
||||
because the server may send data first.
|
||||
"""
|
||||
assert Playbook(udp.UDPLayer(tctx, True)) << OpenConnection(tctx.server)
|
||||
|
||||
tctx.server.timestamp_start = 1624544785
|
||||
assert Playbook(udp.UDPLayer(tctx, True)) << None
|
||||
|
||||
|
||||
def test_open_connection_err(tctx):
|
||||
f = Placeholder(UDPFlow)
|
||||
assert (
|
||||
Playbook(udp.UDPLayer(tctx))
|
||||
<< udp.UdpStartHook(f)
|
||||
>> reply()
|
||||
<< OpenConnection(tctx.server)
|
||||
>> reply("Connect call failed")
|
||||
<< udp.UdpErrorHook(f)
|
||||
>> reply()
|
||||
<< CloseConnection(tctx.client)
|
||||
)
|
||||
|
||||
|
||||
def test_simple(tctx):
|
||||
"""open connection, receive data, send it to peer"""
|
||||
f = Placeholder(UDPFlow)
|
||||
|
||||
assert (
|
||||
Playbook(udp.UDPLayer(tctx))
|
||||
<< udp.UdpStartHook(f)
|
||||
>> reply()
|
||||
<< OpenConnection(tctx.server)
|
||||
>> reply(None)
|
||||
>> DataReceived(tctx.client, b"hello!")
|
||||
<< udp.UdpMessageHook(f)
|
||||
>> reply()
|
||||
<< SendData(tctx.server, b"hello!")
|
||||
>> DataReceived(tctx.server, b"hi")
|
||||
<< udp.UdpMessageHook(f)
|
||||
>> reply()
|
||||
<< SendData(tctx.client, b"hi")
|
||||
>> ConnectionClosed(tctx.server)
|
||||
<< CloseConnection(tctx.client)
|
||||
>> ConnectionClosed(tctx.client)
|
||||
<< udp.UdpEndHook(f)
|
||||
>> reply()
|
||||
>> DataReceived(tctx.server, b"ignored")
|
||||
<< None
|
||||
)
|
||||
assert len(f().messages) == 2
|
||||
|
||||
|
||||
def test_receive_data_before_server_connected(tctx):
|
||||
"""
|
||||
assert that data received before a server connection is established
|
||||
will still be forwarded.
|
||||
"""
|
||||
assert (
|
||||
Playbook(udp.UDPLayer(tctx), hooks=False)
|
||||
<< OpenConnection(tctx.server)
|
||||
>> DataReceived(tctx.client, b"hello!")
|
||||
>> reply(None, to=-2)
|
||||
<< SendData(tctx.server, b"hello!")
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ignore", [True, False])
|
||||
def test_ignore(tctx, ignore):
|
||||
"""
|
||||
no flow hooks when we set ignore.
|
||||
"""
|
||||
|
||||
def no_flow_hooks():
|
||||
assert (
|
||||
Playbook(udp.UDPLayer(tctx, ignore=ignore), hooks=True)
|
||||
<< OpenConnection(tctx.server)
|
||||
>> reply(None)
|
||||
>> DataReceived(tctx.client, b"hello!")
|
||||
<< SendData(tctx.server, b"hello!")
|
||||
)
|
||||
|
||||
if ignore:
|
||||
no_flow_hooks()
|
||||
else:
|
||||
with pytest.raises(AssertionError):
|
||||
no_flow_hooks()
|
||||
|
||||
|
||||
def test_inject(tctx):
|
||||
"""inject data into an open connection."""
|
||||
f = Placeholder(UDPFlow)
|
||||
|
||||
assert (
|
||||
Playbook(udp.UDPLayer(tctx))
|
||||
<< udp.UdpStartHook(f)
|
||||
>> UdpMessageInjected(f, UDPMessage(True, b"hello!"))
|
||||
>> reply(to=-2)
|
||||
<< OpenConnection(tctx.server)
|
||||
>> reply(None)
|
||||
<< udp.UdpMessageHook(f)
|
||||
>> reply()
|
||||
<< SendData(tctx.server, b"hello!")
|
||||
# and the other way...
|
||||
>> UdpMessageInjected(
|
||||
f, UDPMessage(False, b"I have already done the greeting for you.")
|
||||
)
|
||||
<< udp.UdpMessageHook(f)
|
||||
>> reply()
|
||||
<< SendData(tctx.client, b"I have already done the greeting for you.")
|
||||
<< None
|
||||
)
|
||||
assert len(f().messages) == 2
|
@ -62,6 +62,22 @@ def test_tcp_flow(err):
|
||||
assert isinstance(next(i), layers.tcp.TcpEndHook)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("err", [False, True])
|
||||
def test_udp_flow(err):
|
||||
f = tflow.tudpflow(err=err)
|
||||
i = eventsequence.iterate(f)
|
||||
assert isinstance(next(i), layers.udp.UdpStartHook)
|
||||
assert len(f.messages) == 0
|
||||
assert isinstance(next(i), layers.udp.UdpMessageHook)
|
||||
assert len(f.messages) == 1
|
||||
assert isinstance(next(i), layers.udp.UdpMessageHook)
|
||||
assert len(f.messages) == 2
|
||||
if err:
|
||||
assert isinstance(next(i), layers.udp.UdpErrorHook)
|
||||
else:
|
||||
assert isinstance(next(i), layers.udp.UdpEndHook)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"resp, err",
|
||||
[
|
||||
|
@ -400,6 +400,132 @@ class TestMatchingTCPFlow:
|
||||
def test_tcp(self):
|
||||
f = self.flow()
|
||||
assert self.q("~tcp", f)
|
||||
assert not self.q("~udp", f)
|
||||
assert not self.q("~http", f)
|
||||
assert not self.q("~websocket", f)
|
||||
|
||||
def test_ferr(self):
|
||||
e = self.err()
|
||||
assert self.q("~e", e)
|
||||
|
||||
def test_body(self):
|
||||
f = self.flow()
|
||||
|
||||
# Messages sent by client or server
|
||||
assert self.q("~b hello", f)
|
||||
assert self.q("~b me", f)
|
||||
assert not self.q("~b nonexistent", f)
|
||||
|
||||
# Messages sent by client
|
||||
assert self.q("~bq hello", f)
|
||||
assert not self.q("~bq me", f)
|
||||
assert not self.q("~bq nonexistent", f)
|
||||
|
||||
# Messages sent by server
|
||||
assert self.q("~bs me", f)
|
||||
assert not self.q("~bs hello", f)
|
||||
assert not self.q("~bs nonexistent", f)
|
||||
|
||||
def test_src(self):
|
||||
f = self.flow()
|
||||
assert self.q("~src 127.0.0.1", f)
|
||||
assert not self.q("~src foobar", f)
|
||||
assert self.q("~src :22", f)
|
||||
assert not self.q("~src :99", f)
|
||||
assert self.q("~src 127.0.0.1:22", f)
|
||||
|
||||
def test_dst(self):
|
||||
f = self.flow()
|
||||
f.server_conn = tflow.tserver_conn()
|
||||
assert self.q("~dst address", f)
|
||||
assert not self.q("~dst foobar", f)
|
||||
assert self.q("~dst :22", f)
|
||||
assert not self.q("~dst :99", f)
|
||||
assert self.q("~dst address:22", f)
|
||||
|
||||
def test_and(self):
|
||||
f = self.flow()
|
||||
f.server_conn = tflow.tserver_conn()
|
||||
assert self.q("~b hello & ~b me", f)
|
||||
assert not self.q("~src wrongaddress & ~b hello", f)
|
||||
assert self.q("(~src :22 & ~dst :22) & ~b hello", f)
|
||||
assert not self.q("(~src address:22 & ~dst :22) & ~b nonexistent", f)
|
||||
assert not self.q("(~src address:22 & ~dst :99) & ~b hello", f)
|
||||
|
||||
def test_or(self):
|
||||
f = self.flow()
|
||||
f.server_conn = tflow.tserver_conn()
|
||||
assert self.q("~b hello | ~b me", f)
|
||||
assert self.q("~src :22 | ~b me", f)
|
||||
assert not self.q("~src :99 | ~dst :99", f)
|
||||
assert self.q("(~src :22 | ~dst :22) | ~b me", f)
|
||||
|
||||
def test_not(self):
|
||||
f = self.flow()
|
||||
assert not self.q("! ~src :22", f)
|
||||
assert self.q("! ~src :99", f)
|
||||
assert self.q("!~src :99 !~src :99", f)
|
||||
assert not self.q("!~src :99 !~src :22", f)
|
||||
|
||||
def test_request(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~q", f)
|
||||
|
||||
def test_response(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~s", f)
|
||||
|
||||
def test_headers(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~h whatever", f)
|
||||
|
||||
# Request headers
|
||||
assert not self.q("~hq whatever", f)
|
||||
|
||||
# Response headers
|
||||
assert not self.q("~hs whatever", f)
|
||||
|
||||
def test_content_type(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~t whatever", f)
|
||||
|
||||
# Request content-type
|
||||
assert not self.q("~tq whatever", f)
|
||||
|
||||
# Response content-type
|
||||
assert not self.q("~ts whatever", f)
|
||||
|
||||
def test_code(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~c 200", f)
|
||||
|
||||
def test_domain(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~d whatever", f)
|
||||
|
||||
def test_method(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~m whatever", f)
|
||||
|
||||
def test_url(self):
|
||||
f = self.flow()
|
||||
assert not self.q("~u whatever", f)
|
||||
|
||||
|
||||
class TestMatchingUDPFlow:
|
||||
def flow(self):
|
||||
return tflow.tudpflow()
|
||||
|
||||
def err(self):
|
||||
return tflow.tudpflow(err=True)
|
||||
|
||||
def q(self, q, o):
|
||||
return flowfilter.parse(q)(o)
|
||||
|
||||
def test_tcp(self):
|
||||
f = self.flow()
|
||||
assert self.q("~udp", f)
|
||||
assert not self.q("~tcp", f)
|
||||
assert not self.q("~http", f)
|
||||
assert not self.q("~websocket", f)
|
||||
|
||||
|
58
test/mitmproxy/test_udp.py
Normal file
58
test/mitmproxy/test_udp.py
Normal file
@ -0,0 +1,58 @@
|
||||
import pytest
|
||||
|
||||
from mitmproxy import udp
|
||||
from mitmproxy import flowfilter
|
||||
from mitmproxy.test import tflow
|
||||
|
||||
|
||||
class TestUDPFlow:
|
||||
def test_copy(self):
|
||||
f = tflow.tudpflow()
|
||||
f.get_state()
|
||||
f2 = f.copy()
|
||||
a = f.get_state()
|
||||
b = f2.get_state()
|
||||
del a["id"]
|
||||
del b["id"]
|
||||
assert a == b
|
||||
assert not f == f2
|
||||
assert f is not f2
|
||||
|
||||
assert f.messages is not f2.messages
|
||||
|
||||
for m in f.messages:
|
||||
assert m.get_state()
|
||||
m2 = m.copy()
|
||||
assert not m == m2
|
||||
assert m is not m2
|
||||
|
||||
a = m.get_state()
|
||||
b = m2.get_state()
|
||||
assert a == b
|
||||
|
||||
m = udp.UDPMessage(False, "foo")
|
||||
m.set_state(f.messages[0].get_state())
|
||||
assert m.timestamp == f.messages[0].timestamp
|
||||
|
||||
f = tflow.tudpflow(err=True)
|
||||
f2 = f.copy()
|
||||
assert f is not f2
|
||||
assert f.error.get_state() == f2.error.get_state()
|
||||
assert f.error is not f2.error
|
||||
|
||||
def test_match(self):
|
||||
f = tflow.tudpflow()
|
||||
assert not flowfilter.match("~b nonexistent", f)
|
||||
assert flowfilter.match(None, f)
|
||||
assert not flowfilter.match("~b nonexistent", f)
|
||||
|
||||
f = tflow.tudpflow(err=True)
|
||||
assert flowfilter.match("~e", f)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
flowfilter.match("~", f)
|
||||
|
||||
def test_repr(self):
|
||||
f = tflow.tudpflow()
|
||||
assert "UDPFlow" in repr(f)
|
||||
assert "-> " in repr(f.messages[0])
|
@ -57,6 +57,11 @@ def test_generate_tflow_js(tdata):
|
||||
tf_tcp.client_conn.id = "8be32b99-a0b3-446e-93bc-b29982fe1322"
|
||||
tf_tcp.server_conn.id = "e33bb2cd-c07e-4214-9a8e-3a8f85f25200"
|
||||
|
||||
tf_udp = tflow.tudpflow(err=True)
|
||||
tf_udp.id = "f9f7b2b9-7727-4477-822d-d3526e5b8951"
|
||||
tf_udp.client_conn.id = "0a8833da-88e4-429d-ac54-61cda8a7f91c"
|
||||
tf_udp.server_conn.id = "c49f9c2b-a729-4b16-9212-d181717e294b"
|
||||
|
||||
tf_dns = tflow.tdnsflow(resp=True, err=True)
|
||||
tf_dns.id = "5434da94-1017-42fa-872d-a189508d48e4"
|
||||
tf_dns.client_conn.id = "0b4cc0a3-6acb-4880-81c0-1644084126fc"
|
||||
@ -65,13 +70,16 @@ def test_generate_tflow_js(tdata):
|
||||
# language=TypeScript
|
||||
content = (
|
||||
"/** Auto-generated by test_app.py:test_generate_tflow_js */\n"
|
||||
"import {HTTPFlow, TCPFlow, DNSFlow} from '../../flow';\n"
|
||||
"import {HTTPFlow, TCPFlow, UDPFlow, DNSFlow} from '../../flow';\n"
|
||||
"export function THTTPFlow(): Required<HTTPFlow> {\n"
|
||||
" return %s\n"
|
||||
"}\n"
|
||||
"export function TTCPFlow(): Required<TCPFlow> {\n"
|
||||
" return %s\n"
|
||||
"}\n"
|
||||
"export function TUDPFlow(): Required<UDPFlow> {\n"
|
||||
" return %s\n"
|
||||
"}\n"
|
||||
"export function TDNSFlow(): Required<DNSFlow> {\n"
|
||||
" return %s\n"
|
||||
"}\n"
|
||||
@ -82,6 +90,9 @@ def test_generate_tflow_js(tdata):
|
||||
textwrap.indent(
|
||||
json.dumps(app.flow_to_json(tf_tcp), indent=4, sort_keys=True), " "
|
||||
),
|
||||
textwrap.indent(
|
||||
json.dumps(app.flow_to_json(tf_udp), indent=4, sort_keys=True), " "
|
||||
),
|
||||
textwrap.indent(
|
||||
json.dumps(app.flow_to_json(tf_dns), indent=4, sort_keys=True), " "
|
||||
),
|
||||
|
@ -53,6 +53,10 @@
|
||||
background-image: url(images/resourceTcpIcon.png);
|
||||
}
|
||||
|
||||
.resource-icon-udp {
|
||||
background-image: url(images/resourceUdpIcon.png);
|
||||
}
|
||||
|
||||
.resource-icon-dns {
|
||||
background-image: url(images/resourceDnsIcon.png);
|
||||
}
|
||||
|
BIN
web/src/images/resourceUdpIcon.png
Normal file
BIN
web/src/images/resourceUdpIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 936 B |
@ -46,4 +46,12 @@ test("FlowView", async () => {
|
||||
|
||||
fireEvent.click(screen.getByText("Error"));
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
store.dispatch(flowActions.select(store.getState().flows.list[4].id));
|
||||
|
||||
fireEvent.click(screen.getByText("UDP Messages"));
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
fireEvent.click(screen.getByText("Error"));
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ exports[`MainMenu 1`] = `
|
||||
class="form-control"
|
||||
placeholder="Search"
|
||||
type="text"
|
||||
value="~u /second | ~tcp | ~dns"
|
||||
value="~u /second | ~tcp | ~dns | ~udp"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -1430,3 +1430,127 @@ exports[`FlowView 11`] = `
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`FlowView 12`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="flow-detail"
|
||||
>
|
||||
<nav
|
||||
class="nav-tabs nav-tabs-sm"
|
||||
>
|
||||
<a
|
||||
class="active"
|
||||
href="#"
|
||||
>
|
||||
UDP Messages
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="#"
|
||||
>
|
||||
Error
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="#"
|
||||
>
|
||||
Connection
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="#"
|
||||
>
|
||||
Timing
|
||||
</a>
|
||||
</nav>
|
||||
<section
|
||||
class="udp"
|
||||
>
|
||||
<h4>
|
||||
UDP Data
|
||||
</h4>
|
||||
<div
|
||||
class="contentview"
|
||||
>
|
||||
<div
|
||||
class="controls"
|
||||
>
|
||||
<h5>
|
||||
2 Messages
|
||||
</h5>
|
||||
<a
|
||||
class="btn btn-default btn-xs"
|
||||
href="#"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="fa fa-fw fa-files-o"
|
||||
/>
|
||||
|
||||
<b>
|
||||
View:
|
||||
</b>
|
||||
auto
|
||||
<span
|
||||
class="caret"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`FlowView 13`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="flow-detail"
|
||||
>
|
||||
<nav
|
||||
class="nav-tabs nav-tabs-sm"
|
||||
>
|
||||
<a
|
||||
class=""
|
||||
href="#"
|
||||
>
|
||||
UDP Messages
|
||||
</a>
|
||||
<a
|
||||
class="active"
|
||||
href="#"
|
||||
>
|
||||
Error
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="#"
|
||||
>
|
||||
Connection
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="#"
|
||||
>
|
||||
Timing
|
||||
</a>
|
||||
</nav>
|
||||
<section
|
||||
class="error"
|
||||
>
|
||||
<div
|
||||
class="alert alert-warning"
|
||||
>
|
||||
error
|
||||
<div>
|
||||
<small>
|
||||
1999-12-31 23:00:07.000
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/** Auto-generated by test_app.py:test_generate_tflow_js */
|
||||
import {HTTPFlow, TCPFlow, DNSFlow} from '../../flow';
|
||||
import {HTTPFlow, TCPFlow, UDPFlow, DNSFlow} from '../../flow';
|
||||
export function THTTPFlow(): Required<HTTPFlow> {
|
||||
return {
|
||||
"client_conn": {
|
||||
@ -226,6 +226,72 @@ export function TTCPFlow(): Required<TCPFlow> {
|
||||
"type": "tcp"
|
||||
}
|
||||
}
|
||||
export function TUDPFlow(): Required<UDPFlow> {
|
||||
return {
|
||||
"client_conn": {
|
||||
"alpn": "http/1.1",
|
||||
"cert": undefined,
|
||||
"cipher": "cipher",
|
||||
"id": "0a8833da-88e4-429d-ac54-61cda8a7f91c",
|
||||
"peername": [
|
||||
"127.0.0.1",
|
||||
22
|
||||
],
|
||||
"sni": "address",
|
||||
"sockname": [
|
||||
"",
|
||||
0
|
||||
],
|
||||
"timestamp_end": 946681206,
|
||||
"timestamp_start": 946681200,
|
||||
"timestamp_tls_setup": 946681201,
|
||||
"tls_established": true,
|
||||
"tls_version": "TLSv1.2"
|
||||
},
|
||||
"comment": "",
|
||||
"error": {
|
||||
"msg": "error",
|
||||
"timestamp": 946681207.0
|
||||
},
|
||||
"id": "f9f7b2b9-7727-4477-822d-d3526e5b8951",
|
||||
"intercepted": false,
|
||||
"is_replay": undefined,
|
||||
"marked": "",
|
||||
"messages_meta": {
|
||||
"contentLength": 12,
|
||||
"count": 2,
|
||||
"timestamp_last": 946681204.5
|
||||
},
|
||||
"modified": false,
|
||||
"server_conn": {
|
||||
"address": [
|
||||
"address",
|
||||
22
|
||||
],
|
||||
"alpn": undefined,
|
||||
"cert": undefined,
|
||||
"cipher": undefined,
|
||||
"id": "c49f9c2b-a729-4b16-9212-d181717e294b",
|
||||
"peername": [
|
||||
"192.168.0.1",
|
||||
22
|
||||
],
|
||||
"sni": "address",
|
||||
"sockname": [
|
||||
"address",
|
||||
22
|
||||
],
|
||||
"timestamp_end": 946681205,
|
||||
"timestamp_start": 946681202,
|
||||
"timestamp_tcp_setup": 946681203,
|
||||
"timestamp_tls_setup": 946681204,
|
||||
"tls_established": true,
|
||||
"tls_version": "TLSv1.2"
|
||||
},
|
||||
"timestamp_created": 946681200,
|
||||
"type": "udp"
|
||||
}
|
||||
}
|
||||
export function TDNSFlow(): Required<DNSFlow> {
|
||||
return {
|
||||
"client_conn": {
|
||||
|
@ -1,20 +1,21 @@
|
||||
import thunk from 'redux-thunk'
|
||||
import configureStore, {MockStoreCreator, MockStoreEnhanced} from 'redux-mock-store'
|
||||
import {ConnectionState} from '../../ducks/connection'
|
||||
import {TDNSFlow, THTTPFlow, TTCPFlow} from './_tflow'
|
||||
import {TDNSFlow, THTTPFlow, TTCPFlow, TUDPFlow} from './_tflow'
|
||||
import {AppDispatch, RootState} from "../../ducks";
|
||||
import {DNSFlow, HTTPFlow, TCPFlow} from "../../flow";
|
||||
import {DNSFlow, HTTPFlow, TCPFlow, UDPFlow} from "../../flow";
|
||||
import {defaultState as defaultConf} from "../../ducks/conf"
|
||||
import {defaultState as defaultOptions} from "../../ducks/options"
|
||||
|
||||
const mockStoreCreator: MockStoreCreator<RootState, AppDispatch> = configureStore([thunk])
|
||||
|
||||
export {THTTPFlow as TFlow, TTCPFlow}
|
||||
export {THTTPFlow as TFlow, TTCPFlow, TUDPFlow}
|
||||
|
||||
const tflow0: HTTPFlow = THTTPFlow();
|
||||
const tflow1: HTTPFlow = THTTPFlow();
|
||||
const tflow2: TCPFlow = TTCPFlow();
|
||||
const tflow3: DNSFlow = TDNSFlow();
|
||||
const tflow4: UDPFlow = TUDPFlow();
|
||||
tflow0.modified = true
|
||||
tflow0.intercepted = true
|
||||
tflow1.id = "flow2";
|
||||
@ -78,25 +79,28 @@ export const testState: RootState = {
|
||||
[tflow1.id]: tflow1,
|
||||
[tflow2.id]: tflow2,
|
||||
[tflow3.id]: tflow3,
|
||||
[tflow4.id]: tflow4,
|
||||
},
|
||||
filter: '~u /second | ~tcp | ~dns',
|
||||
filter: '~u /second | ~tcp | ~dns | ~udp',
|
||||
highlight: '~u /path',
|
||||
sort: {
|
||||
desc: true,
|
||||
column: "path"
|
||||
},
|
||||
view: [tflow1, tflow2, tflow3],
|
||||
list: [tflow0, tflow1, tflow2, tflow3],
|
||||
view: [tflow1, tflow2, tflow3, tflow4],
|
||||
list: [tflow0, tflow1, tflow2, tflow3, tflow4],
|
||||
listIndex: {
|
||||
[tflow0.id]: 0,
|
||||
[tflow1.id]: 1,
|
||||
[tflow2.id]: 2,
|
||||
[tflow3.id]: 3
|
||||
[tflow3.id]: 3,
|
||||
[tflow4.id]: 4,
|
||||
},
|
||||
viewIndex: {
|
||||
[tflow1.id]: 0,
|
||||
[tflow2.id]: 1,
|
||||
[tflow3.id]: 2,
|
||||
[tflow4.id]: 3,
|
||||
},
|
||||
},
|
||||
connection: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as utils from '../../flow/utils'
|
||||
import {TFlow, TTCPFlow} from "../ducks/tutils";
|
||||
import {TFlow, TTCPFlow, TUDPFlow} from "../ducks/tutils";
|
||||
import {TDNSFlow, THTTPFlow} from "../ducks/_tflow";
|
||||
import {HTTPFlow} from "../../flow";
|
||||
|
||||
@ -73,6 +73,7 @@ describe('isValidHttpVersion', () => {
|
||||
it('should be possible to get a start time', () => {
|
||||
expect(utils.startTime(THTTPFlow())).toEqual(946681200);
|
||||
expect(utils.startTime(TTCPFlow())).toEqual(946681200);
|
||||
expect(utils.startTime(TUDPFlow())).toEqual(946681200);
|
||||
expect(utils.startTime(TDNSFlow())).toEqual(946681200);
|
||||
})
|
||||
|
||||
@ -82,11 +83,13 @@ it('should be possible to get an end time', () => {
|
||||
f.websocket = undefined;
|
||||
expect(utils.endTime(f)).toEqual(946681203);
|
||||
expect(utils.endTime(TTCPFlow())).toEqual(946681205);
|
||||
expect(utils.endTime(TUDPFlow())).toEqual(946681205);
|
||||
expect(utils.endTime(TDNSFlow())).toEqual(946681201);
|
||||
})
|
||||
|
||||
it('should be possible to get a total size', () => {
|
||||
expect(utils.getTotalSize(THTTPFlow())).toEqual(43);
|
||||
expect(utils.getTotalSize(TTCPFlow())).toEqual(12);
|
||||
expect(utils.getTotalSize(TUDPFlow())).toEqual(12);
|
||||
expect(utils.getTotalSize(TDNSFlow())).toEqual(8);
|
||||
})
|
||||
|
@ -37,7 +37,7 @@ icon.headerName = ''
|
||||
icon.sortKey = flow => getIcon(flow)
|
||||
|
||||
const getIcon = (flow: Flow): string => {
|
||||
if (flow.type === "tcp" || flow.type === "dns") {
|
||||
if (flow.type !== "http") {
|
||||
return `resource-icon-${flow.type}`
|
||||
}
|
||||
if (flow.websocket) {
|
||||
@ -76,6 +76,7 @@ const mainPath = (flow: Flow): string => {
|
||||
case "http":
|
||||
return RequestUtils.pretty_url(flow.request)
|
||||
case "tcp":
|
||||
case "udp":
|
||||
return `${flow.client_conn.peername.join(':')} ↔ ${flow.server_conn?.address?.join(':')}`
|
||||
case "dns":
|
||||
return `${flow.request.questions.map(q => `${q.name} ${q.type}`).join(", ")} = ${(flow.response?.answers.map(q => q.data).join(", ") ?? "...") || "?"}`
|
||||
|
@ -12,6 +12,7 @@ import {useAppDispatch, useAppSelector} from "../ducks";
|
||||
import {Flow} from "../flow";
|
||||
import classnames from "classnames";
|
||||
import TcpMessages from "./FlowView/TcpMessages";
|
||||
import UdpMessages from "./FlowView/UdpMessages";
|
||||
|
||||
type TabProps = {
|
||||
flow: Flow
|
||||
@ -24,7 +25,8 @@ export const allTabs: { [name: string]: FunctionComponent<TabProps> & { displayN
|
||||
connection: Connection,
|
||||
timing: Timing,
|
||||
websocket: WebSocket,
|
||||
messages: TcpMessages,
|
||||
tcpmessages: TcpMessages,
|
||||
udpmessages: UdpMessages,
|
||||
dnsrequest: DnsRequest,
|
||||
dnsresponse: DnsResponse,
|
||||
}
|
||||
@ -36,7 +38,10 @@ export function tabsForFlow(flow: Flow): string[] {
|
||||
tabs = ['request', 'response', 'websocket'].filter(k => flow[k])
|
||||
break
|
||||
case "tcp":
|
||||
tabs = ["messages"]
|
||||
tabs = ["tcpmessages"]
|
||||
break
|
||||
case "udp":
|
||||
tabs = ["udpmessages"]
|
||||
break
|
||||
case "dns":
|
||||
tabs = ['request', 'response'].filter(k => flow[k]).map(s => "dns" + s)
|
||||
|
@ -27,8 +27,8 @@ const Questions: React.FC<{
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{message.questions.map(question => (
|
||||
<tr key={question.name}>
|
||||
{message.questions.map((question, index) => (
|
||||
<tr key={index}>
|
||||
<td>{question.name}</td>
|
||||
<td>{question.type}</td>
|
||||
<td>{question.class}</td>
|
||||
@ -57,8 +57,8 @@ const ResourceRecords: React.FC<{
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{values.map(rr => (
|
||||
<tr key={rr.name}>
|
||||
{values.map((rr, index) => (
|
||||
<tr key={index}>
|
||||
<td>{rr.name}</td>
|
||||
<td>{rr.type}</td>
|
||||
<td>{rr.class}</td>
|
||||
|
14
web/src/js/components/FlowView/UdpMessages.tsx
Normal file
14
web/src/js/components/FlowView/UdpMessages.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import {UDPFlow} from "../../flow";
|
||||
import * as React from "react";
|
||||
import Messages from "./Messages";
|
||||
|
||||
|
||||
export default function UdpMessages({flow}: { flow: UDPFlow }) {
|
||||
return (
|
||||
<section className="udp">
|
||||
<h4>UDP Data</h4>
|
||||
<Messages flow={flow} messages_meta={flow.messages_meta}/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
UdpMessages.displayName = "UDP Messages"
|
@ -135,52 +135,55 @@ export default (function() {
|
||||
peg$c95 = "~tcp",
|
||||
peg$c96 = { type: "literal", value: "~tcp", description: "\"~tcp\"" },
|
||||
peg$c97 = function() { return tcpFilter; },
|
||||
peg$c98 = "~tq",
|
||||
peg$c99 = { type: "literal", value: "~tq", description: "\"~tq\"" },
|
||||
peg$c100 = function(s) { return requestContentType(s); },
|
||||
peg$c101 = "~ts",
|
||||
peg$c102 = { type: "literal", value: "~ts", description: "\"~ts\"" },
|
||||
peg$c103 = function(s) { return responseContentType(s); },
|
||||
peg$c104 = "~t",
|
||||
peg$c105 = { type: "literal", value: "~t", description: "\"~t\"" },
|
||||
peg$c106 = function(s) { return contentType(s); },
|
||||
peg$c107 = "~u",
|
||||
peg$c108 = { type: "literal", value: "~u", description: "\"~u\"" },
|
||||
peg$c109 = function(s) { return url(s); },
|
||||
peg$c110 = "~websocket",
|
||||
peg$c111 = { type: "literal", value: "~websocket", description: "\"~websocket\"" },
|
||||
peg$c112 = function() { return websocketFilter; },
|
||||
peg$c113 = { type: "other", description: "integer" },
|
||||
peg$c114 = /^['"]/,
|
||||
peg$c115 = { type: "class", value: "['\"]", description: "['\"]" },
|
||||
peg$c116 = /^[0-9]/,
|
||||
peg$c117 = { type: "class", value: "[0-9]", description: "[0-9]" },
|
||||
peg$c118 = function(digits) { return parseInt(digits.join(""), 10); },
|
||||
peg$c119 = { type: "other", description: "string" },
|
||||
peg$c120 = "\"",
|
||||
peg$c121 = { type: "literal", value: "\"", description: "\"\\\"\"" },
|
||||
peg$c122 = function(chars) { return chars.join(""); },
|
||||
peg$c123 = "'",
|
||||
peg$c124 = { type: "literal", value: "'", description: "\"'\"" },
|
||||
peg$c125 = /^["\\]/,
|
||||
peg$c126 = { type: "class", value: "[\"\\\\]", description: "[\"\\\\]" },
|
||||
peg$c127 = { type: "any", description: "any character" },
|
||||
peg$c128 = function(char) { return char; },
|
||||
peg$c129 = "\\",
|
||||
peg$c130 = { type: "literal", value: "\\", description: "\"\\\\\"" },
|
||||
peg$c131 = /^['\\]/,
|
||||
peg$c132 = { type: "class", value: "['\\\\]", description: "['\\\\]" },
|
||||
peg$c133 = /^['"\\]/,
|
||||
peg$c134 = { type: "class", value: "['\"\\\\]", description: "['\"\\\\]" },
|
||||
peg$c135 = "n",
|
||||
peg$c136 = { type: "literal", value: "n", description: "\"n\"" },
|
||||
peg$c137 = function() { return "\n"; },
|
||||
peg$c138 = "r",
|
||||
peg$c139 = { type: "literal", value: "r", description: "\"r\"" },
|
||||
peg$c140 = function() { return "\r"; },
|
||||
peg$c141 = "t",
|
||||
peg$c142 = { type: "literal", value: "t", description: "\"t\"" },
|
||||
peg$c143 = function() { return "\t"; },
|
||||
peg$c98 = "~udp",
|
||||
peg$c99 = { type: "literal", value: "~udp", description: "\"~udp\"" },
|
||||
peg$c100 = function() { return udpFilter; },
|
||||
peg$c101 = "~tq",
|
||||
peg$c102 = { type: "literal", value: "~tq", description: "\"~tq\"" },
|
||||
peg$c103 = function(s) { return requestContentType(s); },
|
||||
peg$c104 = "~ts",
|
||||
peg$c105 = { type: "literal", value: "~ts", description: "\"~ts\"" },
|
||||
peg$c106 = function(s) { return responseContentType(s); },
|
||||
peg$c107 = "~t",
|
||||
peg$c108 = { type: "literal", value: "~t", description: "\"~t\"" },
|
||||
peg$c109 = function(s) { return contentType(s); },
|
||||
peg$c110 = "~u",
|
||||
peg$c111 = { type: "literal", value: "~u", description: "\"~u\"" },
|
||||
peg$c112 = function(s) { return url(s); },
|
||||
peg$c113 = "~websocket",
|
||||
peg$c114 = { type: "literal", value: "~websocket", description: "\"~websocket\"" },
|
||||
peg$c115 = function() { return websocketFilter; },
|
||||
peg$c116 = { type: "other", description: "integer" },
|
||||
peg$c117 = /^['"]/,
|
||||
peg$c118 = { type: "class", value: "['\"]", description: "['\"]" },
|
||||
peg$c119 = /^[0-9]/,
|
||||
peg$c120 = { type: "class", value: "[0-9]", description: "[0-9]" },
|
||||
peg$c121 = function(digits) { return parseInt(digits.join(""), 10); },
|
||||
peg$c122 = { type: "other", description: "string" },
|
||||
peg$c123 = "\"",
|
||||
peg$c124 = { type: "literal", value: "\"", description: "\"\\\"\"" },
|
||||
peg$c125 = function(chars) { return chars.join(""); },
|
||||
peg$c126 = "'",
|
||||
peg$c127 = { type: "literal", value: "'", description: "\"'\"" },
|
||||
peg$c128 = /^["\\]/,
|
||||
peg$c129 = { type: "class", value: "[\"\\\\]", description: "[\"\\\\]" },
|
||||
peg$c130 = { type: "any", description: "any character" },
|
||||
peg$c131 = function(char) { return char; },
|
||||
peg$c132 = "\\",
|
||||
peg$c133 = { type: "literal", value: "\\", description: "\"\\\\\"" },
|
||||
peg$c134 = /^['\\]/,
|
||||
peg$c135 = { type: "class", value: "['\\\\]", description: "['\\\\]" },
|
||||
peg$c136 = /^['"\\]/,
|
||||
peg$c137 = { type: "class", value: "['\"\\\\]", description: "['\"\\\\]" },
|
||||
peg$c138 = "n",
|
||||
peg$c139 = { type: "literal", value: "n", description: "\"n\"" },
|
||||
peg$c140 = function() { return "\n"; },
|
||||
peg$c141 = "r",
|
||||
peg$c142 = { type: "literal", value: "r", description: "\"r\"" },
|
||||
peg$c143 = function() { return "\r"; },
|
||||
peg$c144 = "t",
|
||||
peg$c145 = { type: "literal", value: "t", description: "\"t\"" },
|
||||
peg$c146 = function() { return "\t"; },
|
||||
|
||||
peg$currPos = 0,
|
||||
peg$savedPos = 0,
|
||||
@ -1354,42 +1357,18 @@ export default (function() {
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 3) === peg$c98) {
|
||||
if (input.substr(peg$currPos, 4) === peg$c98) {
|
||||
s1 = peg$c98;
|
||||
peg$currPos += 3;
|
||||
peg$currPos += 4;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c99); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
s3 = peg$parsews();
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
s3 = peg$parsews();
|
||||
}
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parseStringLiteral();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c100(s3);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c100();
|
||||
}
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 3) === peg$c101) {
|
||||
@ -1430,9 +1409,9 @@ export default (function() {
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 2) === peg$c104) {
|
||||
if (input.substr(peg$currPos, 3) === peg$c104) {
|
||||
s1 = peg$c104;
|
||||
peg$currPos += 2;
|
||||
peg$currPos += 3;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c105); }
|
||||
@ -1506,26 +1485,65 @@ export default (function() {
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 10) === peg$c110) {
|
||||
if (input.substr(peg$currPos, 2) === peg$c110) {
|
||||
s1 = peg$c110;
|
||||
peg$currPos += 10;
|
||||
peg$currPos += 2;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c111); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c112();
|
||||
s2 = [];
|
||||
s3 = peg$parsews();
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
s3 = peg$parsews();
|
||||
}
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parseStringLiteral();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c112(s3);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parseStringLiteral();
|
||||
if (input.substr(peg$currPos, 10) === peg$c113) {
|
||||
s1 = peg$c113;
|
||||
peg$currPos += 10;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c114); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c109(s1);
|
||||
s1 = peg$c115();
|
||||
}
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parseStringLiteral();
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c112(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1565,53 +1583,53 @@ export default (function() {
|
||||
|
||||
peg$silentFails++;
|
||||
s0 = peg$currPos;
|
||||
if (peg$c114.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c117.test(input.charAt(peg$currPos))) {
|
||||
s1 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c115); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c118); }
|
||||
}
|
||||
if (s1 === peg$FAILED) {
|
||||
s1 = null;
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
if (peg$c116.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c119.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c117); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c120); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
while (s3 !== peg$FAILED) {
|
||||
s2.push(s3);
|
||||
if (peg$c116.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c119.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c117); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c120); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
if (peg$c114.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c117.test(input.charAt(peg$currPos))) {
|
||||
s3 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c115); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c118); }
|
||||
}
|
||||
if (s3 === peg$FAILED) {
|
||||
s3 = null;
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c118(s2);
|
||||
s1 = peg$c121(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1628,7 +1646,7 @@ export default (function() {
|
||||
peg$silentFails--;
|
||||
if (s0 === peg$FAILED) {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c113); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c116); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
@ -1640,11 +1658,11 @@ export default (function() {
|
||||
peg$silentFails++;
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 34) {
|
||||
s1 = peg$c120;
|
||||
s1 = peg$c123;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c121); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c124); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
@ -1655,15 +1673,15 @@ export default (function() {
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 34) {
|
||||
s3 = peg$c120;
|
||||
s3 = peg$c123;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c121); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c124); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c122(s2);
|
||||
s1 = peg$c125(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1680,11 +1698,11 @@ export default (function() {
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 39) {
|
||||
s1 = peg$c123;
|
||||
s1 = peg$c126;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c124); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c127); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = [];
|
||||
@ -1695,15 +1713,15 @@ export default (function() {
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 39) {
|
||||
s3 = peg$c123;
|
||||
s3 = peg$c126;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c124); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c127); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c122(s2);
|
||||
s1 = peg$c125(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1742,7 +1760,7 @@ export default (function() {
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c122(s2);
|
||||
s1 = peg$c125(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1757,7 +1775,7 @@ export default (function() {
|
||||
peg$silentFails--;
|
||||
if (s0 === peg$FAILED) {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c119); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c122); }
|
||||
}
|
||||
|
||||
return s0;
|
||||
@ -1769,12 +1787,12 @@ export default (function() {
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$currPos;
|
||||
peg$silentFails++;
|
||||
if (peg$c125.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c128.test(input.charAt(peg$currPos))) {
|
||||
s2 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c126); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c129); }
|
||||
}
|
||||
peg$silentFails--;
|
||||
if (s2 === peg$FAILED) {
|
||||
@ -1789,11 +1807,11 @@ export default (function() {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c127); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c130); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c128(s2);
|
||||
s1 = peg$c131(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1806,17 +1824,17 @@ export default (function() {
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 92) {
|
||||
s1 = peg$c129;
|
||||
s1 = peg$c132;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c130); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c133); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseEscapeSequence();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c128(s2);
|
||||
s1 = peg$c131(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1837,12 +1855,12 @@ export default (function() {
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$currPos;
|
||||
peg$silentFails++;
|
||||
if (peg$c131.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c134.test(input.charAt(peg$currPos))) {
|
||||
s2 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c132); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c135); }
|
||||
}
|
||||
peg$silentFails--;
|
||||
if (s2 === peg$FAILED) {
|
||||
@ -1857,11 +1875,11 @@ export default (function() {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c127); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c130); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c128(s2);
|
||||
s1 = peg$c131(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1874,17 +1892,17 @@ export default (function() {
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 92) {
|
||||
s1 = peg$c129;
|
||||
s1 = peg$c132;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c130); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c133); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseEscapeSequence();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c128(s2);
|
||||
s1 = peg$c131(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1919,11 +1937,11 @@ export default (function() {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c127); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c130); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c128(s2);
|
||||
s1 = peg$c131(s2);
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
@ -1940,53 +1958,53 @@ export default (function() {
|
||||
function peg$parseEscapeSequence() {
|
||||
var s0, s1;
|
||||
|
||||
if (peg$c133.test(input.charAt(peg$currPos))) {
|
||||
if (peg$c136.test(input.charAt(peg$currPos))) {
|
||||
s0 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s0 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c134); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c137); }
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 110) {
|
||||
s1 = peg$c135;
|
||||
s1 = peg$c138;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c136); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c139); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c137();
|
||||
s1 = peg$c140();
|
||||
}
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 114) {
|
||||
s1 = peg$c138;
|
||||
s1 = peg$c141;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c139); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c142); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c140();
|
||||
s1 = peg$c143();
|
||||
}
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 116) {
|
||||
s1 = peg$c141;
|
||||
s1 = peg$c144;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c142); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$c145); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$c143();
|
||||
s1 = peg$c146();
|
||||
}
|
||||
s0 = s1;
|
||||
}
|
||||
@ -2257,6 +2275,12 @@ export default (function() {
|
||||
}
|
||||
tcpFilter.desc = "is a TCP Flow";
|
||||
|
||||
// ~udp
|
||||
function udpFilter(flow){
|
||||
return flow.type === "udp";
|
||||
}
|
||||
udpFilter.desc = "is a UDP Flow";
|
||||
|
||||
// ~tq
|
||||
function requestContentType(regex){
|
||||
regex = new RegExp(regex, "i");
|
||||
|
@ -260,6 +260,12 @@ function tcpFilter(flow){
|
||||
}
|
||||
tcpFilter.desc = "is a TCP Flow";
|
||||
|
||||
// ~udp
|
||||
function udpFilter(flow){
|
||||
return flow.type === "udp";
|
||||
}
|
||||
udpFilter.desc = "is a UDP Flow";
|
||||
|
||||
// ~tq
|
||||
function requestContentType(regex){
|
||||
regex = new RegExp(regex, "i");
|
||||
@ -374,6 +380,7 @@ Expr
|
||||
/ "~src" ws+ s:StringLiteral { return source(s); }
|
||||
/ "~s" { return responseFilter; }
|
||||
/ "~tcp" { return tcpFilter; }
|
||||
/ "~udp" { return udpFilter; }
|
||||
/ "~tq" ws+ s:StringLiteral { return requestContentType(s); }
|
||||
/ "~ts" ws+ s:StringLiteral { return responseContentType(s); }
|
||||
/ "~t" ws+ s:StringLiteral { return contentType(s); }
|
||||
|
@ -15,7 +15,7 @@ interface _Flow {
|
||||
error?: Error
|
||||
}
|
||||
|
||||
export type Flow = HTTPFlow | TCPFlow | DNSFlow;
|
||||
export type Flow = HTTPFlow | TCPFlow | UDPFlow | DNSFlow;
|
||||
|
||||
export interface HTTPFlow extends _Flow {
|
||||
type: "http"
|
||||
@ -29,6 +29,11 @@ export interface TCPFlow extends _Flow {
|
||||
messages_meta: MessagesMeta,
|
||||
}
|
||||
|
||||
export interface UDPFlow extends _Flow {
|
||||
type: "udp"
|
||||
messages_meta: MessagesMeta,
|
||||
}
|
||||
|
||||
export interface Error {
|
||||
msg: string
|
||||
timestamp: number
|
||||
|
@ -133,6 +133,7 @@ export function startTime(flow: Flow): number | undefined {
|
||||
case "http":
|
||||
return flow.request.timestamp_start
|
||||
case "tcp":
|
||||
case "udp":
|
||||
return flow.client_conn.timestamp_start
|
||||
case "dns":
|
||||
return flow.request.timestamp
|
||||
@ -153,6 +154,7 @@ export function endTime(flow: Flow): number | undefined {
|
||||
}
|
||||
return undefined
|
||||
case "tcp":
|
||||
case "udp":
|
||||
return flow.server_conn?.timestamp_end
|
||||
case "dns":
|
||||
return flow.response?.timestamp
|
||||
@ -172,6 +174,7 @@ export const getTotalSize = (flow: Flow): number => {
|
||||
}
|
||||
return total
|
||||
case "tcp":
|
||||
case "udp":
|
||||
return flow.messages_meta.contentLength || 0
|
||||
case "dns":
|
||||
return flow.response?.size ?? 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user