first working

This commit is contained in:
2025-08-03 20:20:55 -04:00
commit cde56494ec
52 changed files with 1893 additions and 0 deletions

5
core/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from core.analyzer import PacketFlowAnalyzer
from core.models import FlowKey
from core.stats import MultiStats
__all__ = ['PacketFlowAnalyzer', 'FlowKey', 'MultiStats']

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

72
core/analyzer.py Normal file
View 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
View 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
View 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