first working
This commit is contained in:
5
core/__init__.py
Normal file
5
core/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from core.analyzer import PacketFlowAnalyzer
|
||||
from core.models import FlowKey
|
||||
from core.stats import MultiStats
|
||||
|
||||
__all__ = ['PacketFlowAnalyzer', 'FlowKey', 'MultiStats']
|
||||
BIN
core/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
core/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/analyzer.cpython-313.pyc
Normal file
BIN
core/__pycache__/analyzer.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/models.cpython-313.pyc
Normal file
BIN
core/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/stats.cpython-313.pyc
Normal file
BIN
core/__pycache__/stats.cpython-313.pyc
Normal file
Binary file not shown.
72
core/analyzer.py
Normal file
72
core/analyzer.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from collections import defaultdict
|
||||
from typing import Optional, List, Type
|
||||
|
||||
import pandas as pd
|
||||
from scapy.all import PcapReader, IP, TCP, UDP, sniff
|
||||
from tabulate import tabulate
|
||||
|
||||
from frametypes import FrameTypeInterface, FRAME_TYPES
|
||||
from core.models import FlowKey
|
||||
from core.stats import MultiStats
|
||||
|
||||
|
||||
class PacketFlowAnalyzer:
|
||||
def __init__(self, stats_classes: List[Type[FrameTypeInterface]] = None):
|
||||
if stats_classes is None:
|
||||
stats_classes = [FRAME_TYPES['overview']]
|
||||
self.stats_classes = stats_classes
|
||||
self.flows = defaultdict(lambda: MultiStats(stats_classes))
|
||||
|
||||
def _get_flow_key(self, pkt) -> Optional[FlowKey]:
|
||||
if not pkt.haslayer(IP):
|
||||
return None
|
||||
|
||||
ip = pkt[IP]
|
||||
if pkt.haslayer(TCP):
|
||||
return FlowKey(ip.src, pkt[TCP].sport, ip.dst, pkt[TCP].dport, "TCP")
|
||||
elif pkt.haslayer(UDP):
|
||||
return FlowKey(ip.src, pkt[UDP].sport, ip.dst, pkt[UDP].dport, "UDP")
|
||||
else:
|
||||
return FlowKey(ip.src, 0, ip.dst, 0, str(ip.proto))
|
||||
|
||||
def _process(self, pkt):
|
||||
key = self._get_flow_key(pkt)
|
||||
if key:
|
||||
self.flows[key].add(float(pkt.time), len(pkt), pkt)
|
||||
|
||||
def analyze_pcap(self, file: str):
|
||||
print(f"Analyzing: {file}")
|
||||
for pkt in PcapReader(file):
|
||||
self._process(pkt)
|
||||
print(f"Found {len(self.flows)} flows")
|
||||
|
||||
def analyze_live(self, iface: str, count: int = 100):
|
||||
print(f"Capturing {count} packets on {iface}")
|
||||
sniff(iface=iface, prn=self._process, count=count)
|
||||
print(f"Found {len(self.flows)} flows")
|
||||
|
||||
def summary(self) -> pd.DataFrame:
|
||||
rows = []
|
||||
for k, multi_stats in self.flows.items():
|
||||
row = {
|
||||
'Src IP': k.src_ip,
|
||||
'Src Port': k.src_port,
|
||||
'Dst IP': k.dst_ip,
|
||||
'Dst Port': k.dst_port,
|
||||
'Proto': k.protocol
|
||||
}
|
||||
if k.extended_type:
|
||||
row['Type'] = k.extended_type
|
||||
row.update(multi_stats.get_combined_summary())
|
||||
rows.append(row)
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
def print_summary(self):
|
||||
df = self.summary()
|
||||
if df.empty:
|
||||
print("No flows detected")
|
||||
return
|
||||
|
||||
print(f"\n{len(df)} flows:")
|
||||
print(tabulate(df, headers='keys', tablefmt='plain', showindex=False))
|
||||
print(f"\nTotals: {df['Pkts'].sum()} packets, {df['Bytes'].sum()} bytes")
|
||||
12
core/models.py
Normal file
12
core/models.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FlowKey:
|
||||
src_ip: str
|
||||
src_port: int
|
||||
dst_ip: str
|
||||
dst_port: int
|
||||
protocol: str
|
||||
extended_type: Optional[str] = None # For extended frame types like IENA, Chapter 10, etc.
|
||||
31
core/stats.py
Normal file
31
core/stats.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import Dict, List, Any, Type
|
||||
|
||||
from scapy.all import Packet
|
||||
|
||||
from frametypes import FrameTypeInterface
|
||||
|
||||
|
||||
class MultiStats:
|
||||
"""Container for multiple stats instances."""
|
||||
def __init__(self, stats_classes: List[Type[FrameTypeInterface]]):
|
||||
self.stats_instances = [cls() for cls in stats_classes]
|
||||
self.stats_classes = stats_classes
|
||||
|
||||
def add(self, timestamp: float, size: int, packet: Packet):
|
||||
for stats in self.stats_instances:
|
||||
stats.add(timestamp, size, packet)
|
||||
|
||||
def get_combined_summary(self) -> Dict[str, Any]:
|
||||
"""Combine summaries from all stats instances."""
|
||||
combined = {}
|
||||
for i, stats in enumerate(self.stats_instances):
|
||||
summary = stats.get_summary_dict()
|
||||
class_name = self.stats_classes[i].__name__.replace('Stats', '')
|
||||
# Add prefix to avoid column name conflicts
|
||||
for key, value in summary.items():
|
||||
if key in ['Pkts', 'Bytes', 'Duration']: # Common columns
|
||||
if i == 0: # Only include once
|
||||
combined[key] = value
|
||||
else:
|
||||
combined[f"{class_name}:{key}"] = value
|
||||
return combined
|
||||
Reference in New Issue
Block a user