Cybersecurity-Projects/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/main.py

460 lines
12 KiB
Python

"""
ⒸAngelaMos | 2026
main.py
Typer CLI commands for network traffic analyzer
Defines the netanal CLI with six commands: capture, analyze, stats, export,
chart, and interfaces. Capture performs live sniffing via CaptureEngine with
optional verbose packet streaming and JSON output. The analyze, stats, export,
and chart commands load from a pcap file through _analyze_pcap_to_stats,
which replays packets through StatisticsCollector.
Key exports:
app - The Typer application instance, used as the entry point in __main__.py
Connects to:
analyzer.py - calls analyze_pcap_file() for all pcap-based commands
capture.py - imports CaptureConfig, CaptureEngine, GracefulCapture, check_capture_permissions, get_available_interfaces
export.py - calls export_to_json(), export_to_csv(), statistics_to_dict()
filters.py - calls validate_bpf_filter() before starting a live capture
output.py - imports console and all print_* functions
statistics.py - creates StatisticsCollector to process replayed pcap packets
visualization.py - calls chart creation functions for the chart command
__init__.py - imports __version__ for the --version flag
"""
import json
from pathlib import Path
from typing import Annotated, Literal
import typer
from netanal import __version__
from netanal.analyzer import analyze_pcap_file
from netanal.capture import (
CaptureConfig,
CaptureEngine,
GracefulCapture,
check_capture_permissions,
get_available_interfaces,
)
from netanal.export import (
export_to_csv,
export_to_json,
statistics_to_dict,
)
from netanal.filters import validate_bpf_filter
from netanal.models import CaptureStatistics, PacketInfo
from netanal.output import (
console,
print_bandwidth_stats,
print_capture_summary,
print_error,
print_interfaces,
print_packet,
print_protocol_table,
print_success,
print_top_talkers,
print_warning,
)
from netanal.statistics import StatisticsCollector
from netanal.visualization import (
create_bandwidth_chart,
create_protocol_bar_chart,
create_top_talkers_chart,
generate_all_charts,
save_chart,
)
ExportFormat = Literal["json", "csv"]
ChartType = Literal["protocols", "top-talkers", "bandwidth", "all"]
app = typer.Typer(
name="netanal",
help="[bold cyan]Network Traffic Analyzer[/bold cyan] - Capture and analyze packets",
rich_markup_mode="rich",
no_args_is_help=True,
)
def _analyze_pcap_to_stats(pcap_file: Path) -> tuple[CaptureStatistics,
list[PacketInfo]]:
"""
Analyze a PCAP file and return statistics with packet list
"""
packets = analyze_pcap_file(str(pcap_file))
collector = StatisticsCollector()
collector.start()
for packet in packets:
collector.record_packet(packet)
return collector.get_statistics(), packets
def version_callback(value: bool) -> None:
"""
Display version and exit
"""
if value:
console.print(
f"[bold cyan]netanal[/bold cyan] version [green]{__version__}[/green]"
)
raise typer.Exit()
@app.callback()
def main(
version: Annotated[
bool | None,
typer.Option(
"--version",
"-v",
help="Show version and exit",
callback=version_callback,
is_eager=True,
),
] = None,
) -> None:
"""
[bold cyan]Network Traffic Analyzer[/bold cyan]
Capture and analyze network packets with protocol distribution,
top talkers identification, and bandwidth visualization.
"""
@app.command()
def capture(
interface: Annotated[
str | None,
typer.Option(
"--interface",
"-i",
help="Network interface to capture on",
),
] = None,
filter_expr: Annotated[
str | None,
typer.Option(
"--filter",
"-f",
help="BPF filter expression",
),
] = None,
count: Annotated[
int | None,
typer.Option(
"--count",
"-c",
help="Number of packets to capture",
),
] = None,
timeout: Annotated[
float | None,
typer.Option(
"--timeout",
"-t",
help="Capture timeout in seconds",
),
] = None,
output: Annotated[
Path | None,
typer.Option(
"--output",
"-o",
help="Output file for results (JSON)",
),
] = None,
verbose: Annotated[
bool,
typer.Option(
"--verbose",
help="Show individual packets",
),
] = False,
) -> None:
"""
[bold green]Capture[/bold green] live network packets
Examples:
netanal capture -i eth0 --count 100
netanal capture --filter "tcp port 80" --timeout 30
netanal capture -i lo -c 50 --verbose
"""
can_capture, msg = check_capture_permissions()
if not can_capture:
print_error(f"Cannot capture packets: {msg}")
raise typer.Exit(1)
if filter_expr and not validate_bpf_filter(filter_expr):
print_error(f"Invalid BPF filter: {filter_expr}")
raise typer.Exit(1)
config = CaptureConfig(
interface=interface,
bpf_filter=filter_expr,
packet_count=count,
timeout_seconds=timeout,
)
packets_captured: list[PacketInfo] = []
def on_packet(packet: PacketInfo) -> None:
if verbose:
print_packet(packet)
if output:
packets_captured.append(packet)
console.print(
f"[cyan]Starting capture on {interface or 'all interfaces'}...[/cyan]"
)
console.print("[dim]Press Ctrl+C to stop[/dim]\n")
engine = CaptureEngine(
config=config,
on_packet=on_packet if verbose or output else None
)
with GracefulCapture(engine) as cap:
stats = cap.wait()
console.print()
print_capture_summary(stats)
print_protocol_table(stats)
print_top_talkers(stats)
if output:
export_to_json(stats, output, packets_captured)
print_success(f"Results saved to {output}")
@app.command()
def analyze(
pcap_file: Annotated[
Path,
typer.Argument(help="PCAP file to analyze"),
],
top_talkers: Annotated[
int,
typer.Option(
"--top-talkers",
"-t",
help="Show top N talkers",
),
] = 10,
json_output: Annotated[
bool,
typer.Option(
"--json",
"-j",
help="Output results as JSON",
),
] = False,
) -> None:
"""
[bold green]Analyze[/bold green] packets from a PCAP file
Examples:
netanal analyze traffic.pcap
netanal analyze traffic.pcap --top-talkers 20
netanal analyze traffic.pcap --json
"""
if not pcap_file.exists():
print_error(f"File not found: {pcap_file}")
raise typer.Exit(1)
console.print(f"[cyan]Analyzing {pcap_file}...[/cyan]")
stats, _ = _analyze_pcap_to_stats(pcap_file)
if json_output:
console.print(json.dumps(statistics_to_dict(stats), indent=2))
else:
print_capture_summary(stats)
print_protocol_table(stats)
print_top_talkers(stats, limit=top_talkers)
@app.command()
def stats(
pcap_file: Annotated[
Path,
typer.Argument(help="PCAP file to analyze"),
],
bandwidth: Annotated[
bool,
typer.Option(
"--bandwidth",
"-b",
help="Show bandwidth statistics",
),
] = False,
protocols: Annotated[
bool,
typer.Option(
"--protocols",
"-p",
help="Show protocol distribution",
),
] = False,
endpoints: Annotated[
bool,
typer.Option(
"--endpoints",
"-e",
help="Show endpoint statistics",
),
] = False,
) -> None:
"""
[bold green]Display statistics[/bold green] from a PCAP file
Examples:
netanal stats traffic.pcap --bandwidth
netanal stats traffic.pcap --protocols --endpoints
"""
if not pcap_file.exists():
print_error(f"File not found: {pcap_file}")
raise typer.Exit(1)
stats_result, _ = _analyze_pcap_to_stats(pcap_file)
print_capture_summary(stats_result)
if protocols or (not bandwidth and not endpoints):
print_protocol_table(stats_result)
if endpoints:
print_top_talkers(stats_result, limit=20)
if bandwidth:
print_bandwidth_stats(stats_result)
@app.command("export")
def export_cmd(
pcap_file: Annotated[
Path,
typer.Argument(help="PCAP file to export"),
],
output: Annotated[
Path,
typer.Option(
"--output",
"-o",
help="Output file path",
),
],
format_type: Annotated[
ExportFormat,
typer.Option(
"--format",
"-f",
help="Output format",
),
] = "json",
) -> None:
"""
[bold green]Export[/bold green] capture data to CSV or JSON
Examples:
netanal export traffic.pcap -o results.json -f json
netanal export traffic.pcap -o packets.csv -f csv
"""
if not pcap_file.exists():
print_error(f"File not found: {pcap_file}")
raise typer.Exit(1)
stats, packets = _analyze_pcap_to_stats(pcap_file)
if format_type == "json":
export_to_json(stats, output, packets)
elif format_type == "csv":
export_to_csv(stats, output, packets)
print_success(f"Exported to {output}")
@app.command()
def chart(
pcap_file: Annotated[
Path,
typer.Argument(help="PCAP file to visualize"),
],
chart_type: Annotated[
ChartType,
typer.Option(
"--type",
"-t",
help="Chart type",
),
] = "all",
output: Annotated[
Path | None,
typer.Option(
"--output",
"-o",
help="Output file path (for single chart)",
),
] = None,
output_dir: Annotated[
Path | None,
typer.Option(
"--output-dir",
"-d",
help="Output directory (for all charts)",
),
] = None,
) -> None:
"""
[bold green]Generate charts[/bold green] from capture data
Examples:
netanal chart traffic.pcap --type protocols -o protocols.png
netanal chart traffic.pcap --type all -d ./charts/
"""
if not pcap_file.exists():
print_error(f"File not found: {pcap_file}")
raise typer.Exit(1)
stats, _ = _analyze_pcap_to_stats(pcap_file)
if chart_type == "all":
out_dir = output_dir or Path()
generated = generate_all_charts(stats, out_dir)
for path in generated:
print_success(f"Generated {path}")
else:
if not output:
output = Path(f"{chart_type}.png")
if chart_type == "protocols":
fig = create_protocol_bar_chart(stats)
elif chart_type == "top-talkers":
fig = create_top_talkers_chart(stats)
else:
fig = create_bandwidth_chart(stats)
save_chart(fig, output)
print_success(f"Generated {output}")
@app.command()
def interfaces() -> None:
"""
[bold green]List[/bold green] available network interfaces
"""
ifaces = get_available_interfaces()
if not ifaces:
print_warning("No interfaces found")
raise typer.Exit(0)
print_interfaces(ifaces)
if __name__ == "__main__":
app()