mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-20 09:52:22 +03:00
Marker pcaps are written by uBridge on the compute that hosts the capture node. The tag replay only looked in the project folder on the controller. So a source from a remote compute showed count 0 and no frames, with no error. Its frame detail returned 404 and said nothing ever matched (#3010). Each source now gets its pcap through the compute of its capture node. Local files are read as before. Remote files are fetched over HTTP on every acquire into a small cache (32 idle files, cleared at shutdown). A file is replaced only when its bytes changed. This keeps the old mtime, so the sharkd session for that file stays alive. When a compute cannot be reached, its source now shows an error message in the timeline instead of just showing nothing. The detail endpoint now returns an error instead of 404.
118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
#
|
|
# Copyright (C) 2026 GNS3 Technologies Inc.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from typing import List, Optional
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class ReplaySource(BaseModel):
|
|
"""
|
|
A capture source (one marker pcap) of a replay tag.
|
|
"""
|
|
|
|
node_id: str
|
|
link_id: str
|
|
marker: str
|
|
data_link_type: str
|
|
count: int = Field(..., description="Total frames in the capture, unaffected by filters")
|
|
error: Optional[str] = Field(
|
|
None, description="Set when the capture file could not be fetched from the compute hosting it"
|
|
)
|
|
|
|
|
|
class ReplayFrame(BaseModel):
|
|
"""
|
|
One frame of the merged replay timeline.
|
|
"""
|
|
|
|
ts: str = Field(..., description="Timestamp string to send back unchanged")
|
|
len: int = Field(..., description="Captured length in bytes")
|
|
node_id: str
|
|
link_id: str
|
|
marker: str
|
|
frame_number: int = Field(..., description="1-based frame number in the source pcap")
|
|
src: Optional[str] = None
|
|
dst: Optional[str] = None
|
|
proto: Optional[str] = None
|
|
info: Optional[str] = None
|
|
bg: Optional[str] = Field(None, description="Wireshark coloring rule background")
|
|
fg: Optional[str] = Field(None, description="Wireshark coloring rule foreground")
|
|
|
|
|
|
class ReplayTimeline(BaseModel):
|
|
"""
|
|
Replay timeline of a tag.
|
|
"""
|
|
|
|
tag: int
|
|
start: Optional[str] = Field(None, description="Timestamp of the first matching frame")
|
|
end: Optional[str] = Field(None, description="Timestamp of the last matching frame")
|
|
frame_count: int
|
|
sources: List[ReplaySource]
|
|
frames: List[ReplayFrame]
|
|
|
|
|
|
class ReplayFrames(BaseModel):
|
|
"""
|
|
Frames of a replay time window.
|
|
"""
|
|
|
|
frames: List[ReplayFrame]
|
|
|
|
|
|
class ReplayFrameSource(BaseModel):
|
|
"""
|
|
The source pcap and frame a decoded frame comes from.
|
|
"""
|
|
|
|
node_id: str
|
|
link_id: str
|
|
marker: str
|
|
frame_number: int
|
|
|
|
|
|
class ReplayTreeNode(BaseModel):
|
|
"""
|
|
A node of the decoded protocol tree.
|
|
|
|
Keys sharkd adds in the future are passed through unchanged.
|
|
"""
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
element: Optional[str] = Field(None, description="Node type")
|
|
label: Optional[str] = Field(None, description="Display text")
|
|
name: Optional[str] = Field(None, description="Field name, e.g. 'ip.ttl'")
|
|
filter_expr: Optional[str] = Field(None, description="Display filter with the value baked in")
|
|
pos: Optional[int] = Field(None, description="Byte offset for hex highlighting")
|
|
size: Optional[int] = Field(None, description="Byte length for hex highlighting")
|
|
expert: Optional[str] = Field(None, description="Expert severity")
|
|
generated: Optional[bool] = Field(None, description="Generated by Wireshark")
|
|
children: Optional[List["ReplayTreeNode"]] = Field(None, description="Nested fields")
|
|
|
|
|
|
class ReplayFrameDetail(BaseModel):
|
|
"""
|
|
One decoded frame.
|
|
"""
|
|
|
|
ts: str
|
|
source: ReplayFrameSource
|
|
field_count: int
|
|
hex: str = Field(..., description="Raw frame bytes as hex")
|
|
tree: List[ReplayTreeNode]
|