Graph traceroutes.

This commit is contained in:
Jason Michalski
2024-06-09 21:39:36 -07:00
parent 7205fabed5
commit 551852e387
5 changed files with 121 additions and 1 deletions
+10
View File
@@ -51,3 +51,13 @@ class PacketSeen(Base):
rx_rssi: Mapped[int] = mapped_column(nullable=True)
topic: Mapped[str]
import_time: Mapped[datetime]
class Traceroute(Base):
__tablename__ = "traceroute"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
packet_id = mapped_column(ForeignKey("packet.id"))
gateway_node_id: Mapped[int] = mapped_column(BigInteger)
done: Mapped[bool]
route: Mapped[bytes]
+22 -1
View File
@@ -6,7 +6,7 @@ from meshtastic.portnums_pb2 import PortNum
from meshtastic.mesh_pb2 import User, HardwareModel
from meshview import database
from meshview import decode_payload
from meshview.models import Packet, PacketSeen, Node
from meshview.models import Packet, PacketSeen, Node, Traceroute
from meshview import notify
@@ -99,6 +99,18 @@ async def process_envelope(topic, env):
node.last_long = position.longitude_i
session.add(node)
if env.packet.decoded.portnum == PortNum.TRACEROUTE_APP:
if env.packet.decoded.want_response:
packet_id = env.packet.id
else:
packet_id = env.packet.decoded.request_id
session.add(Traceroute(
packet_id=packet_id,
route=env.packet.decoded.payload,
done=not env.packet.decoded.want_response,
gateway_node_id=int(env.gateway_id[1:], 16),
))
await session.commit()
if new_packet:
await packet.awaitable_attrs.to_node
@@ -188,3 +200,12 @@ async def has_packets(node_id, portnum):
select(Packet.id).where(Packet.from_node_id == node_id).limit(1)
)).scalar()
)
async def get_traceroute(packet_id):
async with database.async_session() as session:
result = await session.execute(
select(Traceroute)
.where(Traceroute.packet_id == packet_id)
)
return result.scalars()
+3
View File
@@ -53,6 +53,9 @@
</li>
{% endfor %}
</ul>
{% if packet.raw_mesh_packet.decoded.want_response %}
<a href="/graph/traceroute/{{packet.id}}">graph</a>
{% endif %}
{% endif %}
<pre>{{packet.payload}}</pre>
</dd>
+85
View File
@@ -7,6 +7,7 @@ from aiohttp_sse import sse_response
import ssl
import re
import pydot
from pandas import DataFrame
import seaborn as sns
import matplotlib.pyplot as plt
@@ -528,6 +529,90 @@ async def graph_neighbors(request):
)
@routes.get("/graph/traceroute/{packet_id}")
async def graph_traceroute(request):
packet_id = int(request.match_info['packet_id'])
traceroutes = list(await store.get_traceroute(packet_id))
packet = await store.get_packet(packet_id)
node_ids = set()
for tr in traceroutes:
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
node_ids.add(tr.gateway_node_id)
for node_id in route.route:
node_ids.add(node_id)
node_ids.add(packet.from_node_id)
node_ids.add(packet.to_node_id)
nodes = {}
async with asyncio.TaskGroup() as tg:
for node_id in node_ids:
nodes[node_id] = tg.create_task(store.get_node(node_id))
graph = pydot.Dot('traceroute', graph_type="digraph")
paths = set()
node_color = {}
mqtt_nodes = set()
saw_reply = set()
dest = None
for tr in traceroutes:
if tr.done:
saw_reply.add(tr.gateway_node_id)
if tr.done and dest:
continue
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
path = [packet.from_node_id]
path.extend(route.route)
if tr.done:
dest = packet.to_node_id
path.append(packet.to_node_id)
elif path[-1] != tr.gateway_node_id:
# It seems some nodes add them self to the list before uplinking
path.append(tr.gateway_node_id)
mqtt_nodes.add(tr.gateway_node_id)
node_color[path[-1]] = '#' + hex(hash(tuple(path)))[3:9]
paths.add(tuple(path))
used_nodes = set()
for path in paths:
used_nodes.update(path)
for node_id in used_nodes:
node = await nodes[node_id]
if not node:
node_name = node_id_to_hex(node_id)
else:
node_name = f'[{node.short_name}] {node.long_name} - {node_id_to_hex(node_id)}'
style = 'dashed'
if node_id == dest:
style = 'filled'
elif node_id in mqtt_nodes:
style = 'solid'
if node_id in saw_reply:
style += ', diagonals'
graph.add_node(pydot.Node(
str(node_id),
label=node_name,
shape='box',
color=node_color.get(node_id, 'black'),
style=style,
))
for path in paths:
color = '#' + hex(hash(tuple(path)))[3:9]
for src, dest in zip(path, path[1:]):
graph.add_edge(pydot.Edge(src, dest, color=color))
return web.Response(
body=graph.create_svg(),
content_type="image/svg+xml",
)
async def run_server(bind, port, tls_cert):
app = web.Application()
app.add_routes(routes)
+1
View File
@@ -9,3 +9,4 @@ Jinja2
aiohttp-sse
asyncpg
seaborn
pydot