Spaces:
Sleeping
Sleeping
import pyshark | |
import threading | |
import time | |
import numpy as np | |
import pandas as pd | |
from queue import Queue, Empty | |
import netifaces as net | |
import os | |
import joblib | |
from threading import Lock | |
import streamlit as st | |
current_dir = os.path.dirname(os.path.abspath(__file__)) | |
MODEL_PATH = os.path.join(current_dir, 'Anomaly_Model.joblib') | |
flow_dict_lock = threading.Lock() | |
FEATURE_NAMES = [ | |
' Destination Port', | |
' Flow Duration', | |
' Total Fwd Packets', | |
' Total Backward Packets', | |
'Total Length of Fwd Packets', | |
' Total Length of Bwd Packets', | |
' Fwd Packet Length Max', | |
' Fwd Packet Length Min', | |
' Fwd Packet Length Mean', | |
' Fwd Packet Length Std', | |
'Bwd Packet Length Max', | |
' Bwd Packet Length Min', | |
' Bwd Packet Length Mean', | |
' Bwd Packet Length Std', | |
'Flow Bytes/s', | |
' Flow Packets/s', | |
' Flow IAT Mean', | |
' Flow IAT Std', | |
' Flow IAT Max', | |
' Flow IAT Min', | |
'Fwd IAT Total', | |
' Fwd IAT Mean', | |
' Fwd IAT Std', | |
' Fwd IAT Max', | |
' Fwd IAT Min', | |
'Bwd IAT Total', | |
' Bwd IAT Mean', | |
' Bwd IAT Std', | |
' Bwd IAT Max', | |
' Bwd IAT Min', | |
'Fwd PSH Flags', | |
' Bwd PSH Flags', | |
' Fwd URG Flags', | |
' Bwd URG Flags', | |
' Fwd Header Length', | |
' Bwd Header Length', | |
'Fwd Packets/s', | |
' Bwd Packets/s', | |
' Min Packet Length', | |
' Max Packet Length', | |
' Packet Length Mean', | |
' Packet Length Std', | |
' Packet Length Variance', | |
'FIN Flag Count', | |
' SYN Flag Count', | |
' RST Flag Count', | |
' PSH Flag Count', | |
' ACK Flag Count', | |
' URG Flag Count', | |
' CWE Flag Count', | |
' ECE Flag Count', | |
' Down/Up Ratio', | |
' Average Packet Size', | |
' Avg Fwd Segment Size', | |
' Avg Bwd Segment Size', | |
' Fwd Header Length.1', | |
'Fwd Avg Bytes/Bulk', | |
' Fwd Avg Packets/Bulk', | |
' Fwd Avg Bulk Rate', | |
' Bwd Avg Bytes/Bulk', | |
' Bwd Avg Packets/Bulk', | |
'Bwd Avg Bulk Rate', | |
'Subflow Fwd Packets', | |
' Subflow Fwd Bytes', | |
' Subflow Bwd Packets', | |
' Subflow Bwd Bytes', | |
'Init_Win_bytes_forward', | |
' Init_Win_bytes_backward', | |
' act_data_pkt_fwd', | |
' min_seg_size_forward', | |
'Active Mean', | |
' Active Std', | |
' Active Max', | |
' Active Min', | |
'Idle Mean', | |
' Idle Std', | |
' Idle Max', | |
' Idle Min' | |
] | |
# Add verification after loading the model | |
def verify_features(): | |
"""Verify that we have all required features and they match exactly""" | |
print(f"\nFeature Verification:") | |
print(f"Total features in training data: {len(FEATURE_NAMES)}") | |
print(f"Features in loaded model: {len(pipeline['selected_features'])}") | |
# Check for missing features | |
missing_features = set(FEATURE_NAMES) - set(pipeline['selected_features']) | |
if missing_features: | |
print("\nWARNING: Missing features in model:") | |
for feature in missing_features: | |
print(f"- {feature}") | |
# Check for extra features | |
extra_features = set(pipeline['selected_features']) - set(FEATURE_NAMES) | |
if extra_features: | |
print("\nWARNING: Extra features in model:") | |
for feature in extra_features: | |
print(f"- {feature}") | |
# Print first few features for verification | |
print("\nFirst 5 features:") | |
for i, feature in enumerate(FEATURE_NAMES[:5]): | |
print(f"{i+1}. '{feature}'") | |
# Add these constants at the top | |
PACKET_TIMEOUT = 60 # Flow expiration timeout in seconds | |
QUEUE_SIZE = 10000 # Maximum packet queue size | |
VERBOSE = False # Enable/disable detailed logging | |
# Add a packet counter class | |
class PacketStats: | |
def __init__(self): | |
self.total_packets = 0 | |
self.ddos_flows = 0 | |
self.benign_flows = 0 | |
self.start_time = time.time() | |
self.lock = threading.Lock() | |
def update_stats(self, is_ddos): | |
with self.lock: | |
self.total_packets += 1 | |
if is_ddos: | |
self.ddos_flows += 1 | |
else: | |
self.benign_flows += 1 | |
def print_stats(self): | |
with self.lock: | |
elapsed_time = time.time() - self.start_time | |
print(f"\nMonitoring Statistics:") | |
print(f"Running time: {elapsed_time:.2f} seconds") | |
print(f"Total packets processed: {self.total_packets}") | |
print(f"DDoS flows detected: {self.ddos_flows}") | |
print(f"Benign flows detected: {self.benign_flows}") | |
try: | |
print("Loading model...") | |
# Use the constructed path to load the model | |
model_data = joblib.load(MODEL_PATH) | |
pipeline = { | |
'model': model_data['model'], | |
'scaler': model_data['model'].named_steps['scaler'], | |
'selector': model_data['model'].named_steps['feature_selection'], | |
'variance_selector': model_data['model'].named_steps['variance_threshold'], | |
'selected_features': model_data['feature_names'] | |
} | |
print("Model loaded successfully") | |
except Exception as e: | |
print(f"Error loading model: {e}") | |
raise | |
class Flow: | |
def is_expired(self, timeout=60): | |
return (time.time() - self.flow_end_time) > timeout | |
def __init__(self, src_ip, src_port, dst_ip, dst_port, protocol): | |
# Flow identifiers | |
self.src_ip = src_ip | |
self.src_port = src_port | |
self.dst_ip = dst_ip | |
self.dst_port = dst_port | |
self.protocol = protocol | |
# Packet tracking | |
self.total_fwd_packets = 0 | |
self.total_bwd_packets = 0 | |
self.total_length_fwd_packets = 0 | |
self.total_length_bwd_packets = 0 | |
# Packet lengths | |
self.fwd_packet_lengths = [] | |
self.bwd_packet_lengths = [] | |
self.packet_lengths = [] # All packet lengths | |
# Inter-arrival times | |
self.fwd_iat = [] | |
self.bwd_iat = [] | |
self.flow_iat = [] | |
self.last_fwd_packet_time = None | |
self.last_bwd_packet_time = None | |
self.last_packet_time = None | |
# Header lengths | |
self.fwd_header_length = 0 | |
self.bwd_header_length = 0 | |
# Flags | |
self.fin_flag_count = 0 | |
self.syn_flag_count = 0 | |
self.rst_flag_count = 0 | |
self.psh_flag_count = 0 | |
self.ack_flag_count = 0 | |
self.urg_flag_count = 0 | |
self.cwe_flag_count = 0 | |
self.ece_flag_count = 0 | |
# Window sizes | |
self.init_win_bytes_forward = None | |
self.init_win_bytes_backward = None | |
self.act_data_pkt_fwd = 0 | |
self.min_seg_size_forward = None | |
# Active and Idle times | |
self.flow_start_time = time.time() | |
self.flow_end_time = self.flow_start_time | |
self.active_times = [] | |
self.idle_times = [] | |
self.last_active_time = None | |
# Other features | |
self.flow_packet_times = [] | |
def add_packet(self, packet, direction): | |
try: | |
if not hasattr(packet, 'sniff_timestamp'): | |
if VERBOSE: | |
print(f"Packet missing sniff_timestamp: {packet}") | |
return | |
current_time = float(packet.sniff_timestamp) | |
self.flow_end_time = current_time | |
# Track packet times for flow IAT | |
self.flow_packet_times.append(current_time) | |
if len(self.flow_packet_times) > 1: | |
iat = self.flow_packet_times[-1] - self.flow_packet_times[-2] | |
self.flow_iat.append(iat) | |
# Packet length - add error checking | |
try: | |
packet_length = int(packet.length) | |
except (AttributeError, ValueError) as e: | |
if VERBOSE: | |
print(f"Error getting packet length: {e}, packet: {packet}") | |
return | |
self.packet_lengths.append(packet_length) | |
# Detailed error handling for IP addresses | |
try: | |
if hasattr(packet, 'ip'): | |
src_ip = packet.ip.src | |
dst_ip = packet.ip.dst | |
elif hasattr(packet, 'ipv6'): | |
src_ip = packet.ipv6.src | |
dst_ip = packet.ipv6.dst | |
else: | |
if VERBOSE: | |
print(f"Packet has no IP layer: {packet}") | |
return | |
except AttributeError as e: | |
if VERBOSE: | |
print(f"Error accessing IP addresses: {e}, packet: {packet}") | |
return | |
current_time = float(packet.sniff_timestamp) | |
self.flow_end_time = current_time | |
# Track packet times for flow IAT | |
self.flow_packet_times.append(current_time) | |
if len(self.flow_packet_times) > 1: | |
iat = self.flow_packet_times[-1] - self.flow_packet_times[-2] | |
self.flow_iat.append(iat) | |
# Packet length | |
packet_length = int(packet.length) | |
self.packet_lengths.append(packet_length) | |
# Header length calculation | |
header_length = 14 # Ethernet header | |
if hasattr(packet, 'ip'): | |
src_ip = packet.ip.src | |
dst_ip = packet.ip.dst | |
elif hasattr(packet, 'ipv6'): | |
src_ip = packet.ipv6.src | |
dst_ip = packet.ipv6.dst | |
else: | |
return | |
if hasattr(packet, 'tcp'): | |
header_length += int(packet.tcp.hdr_len or 0) | |
if hasattr(packet.tcp, 'flags'): | |
flags = int(packet.tcp.flags_hex, 16) | |
self.fin_flag_count += bool(flags & 0x01) | |
self.syn_flag_count += bool(flags & 0x02) | |
self.rst_flag_count += bool(flags & 0x04) | |
self.psh_flag_count += bool(flags & 0x08) | |
self.ack_flag_count += bool(flags & 0x10) | |
self.urg_flag_count += bool(flags & 0x20) | |
self.ece_flag_count += bool(flags & 0x40) | |
self.cwe_flag_count += bool(flags & 0x80) | |
if direction == 'forward' and self.init_win_bytes_forward is None: | |
self.init_win_bytes_forward = int(packet.tcp.window_size or 0) | |
elif direction == 'backward' and self.init_win_bytes_backward is None: | |
self.init_win_bytes_backward = int(packet.tcp.window_size or 0) | |
if self.min_seg_size_forward is None: | |
self.min_seg_size_forward = int(packet.tcp.hdr_len or 0) | |
elif hasattr(packet, 'udp'): | |
header_length += 8 | |
if direction == 'forward': | |
self.total_fwd_packets += 1 | |
self.total_length_fwd_packets += packet_length | |
self.fwd_packet_lengths.append(packet_length) | |
self.fwd_header_length += header_length | |
self.act_data_pkt_fwd += 1 | |
if self.last_fwd_packet_time is not None: | |
iat = current_time - self.last_fwd_packet_time | |
self.fwd_iat.append(iat) | |
self.last_fwd_packet_time = current_time | |
else: | |
self.total_bwd_packets += 1 | |
self.total_length_bwd_packets += packet_length | |
self.bwd_packet_lengths.append(packet_length) | |
self.bwd_header_length += header_length | |
if self.last_bwd_packet_time is not None: | |
iat = current_time - self.last_bwd_packet_time | |
self.bwd_iat.append(iat) | |
self.last_bwd_packet_time = current_time | |
except Exception as e: | |
if VERBOSE: | |
print(f"Error processing packet: {str(e)}") | |
print(f"Packet details: {packet}") | |
import traceback | |
print(traceback.format_exc()) | |
def compute_features(self): | |
# Compute statistical features for packet lengths | |
fwd_pl_array = np.array(self.fwd_packet_lengths) | |
bwd_pl_array = np.array(self.bwd_packet_lengths) | |
all_pl_array = np.array(self.packet_lengths) | |
# Handle empty arrays | |
if len(fwd_pl_array) == 0: | |
fwd_pl_array = np.array([0]) | |
if len(bwd_pl_array) == 0: | |
bwd_pl_array = np.array([0]) | |
if len(all_pl_array) == 0: | |
all_pl_array = np.array([0]) | |
if len(self.fwd_iat) == 0: | |
self.fwd_iat = [0] | |
if len(self.bwd_iat) == 0: | |
self.bwd_iat = [0] | |
if len(self.flow_iat) == 0: | |
self.flow_iat = [0] | |
flow_duration = (self.flow_end_time - self.flow_start_time) * 1e6 # in microseconds | |
# Compute features | |
features = { | |
' Destination Port': self.dst_port, | |
' Flow Duration': flow_duration, | |
' Total Fwd Packets': self.total_fwd_packets, | |
' Total Backward Packets': self.total_bwd_packets, | |
'Total Length of Fwd Packets': self.total_length_fwd_packets, | |
' Total Length of Bwd Packets': self.total_length_bwd_packets, | |
' Fwd Packet Length Max': np.max(fwd_pl_array), | |
' Fwd Packet Length Min': np.min(fwd_pl_array), | |
' Fwd Packet Length Mean': np.mean(fwd_pl_array), | |
' Fwd Packet Length Std': np.std(fwd_pl_array), | |
'Bwd Packet Length Max': np.max(bwd_pl_array), | |
' Bwd Packet Length Min': np.min(bwd_pl_array), | |
' Bwd Packet Length Mean': np.mean(bwd_pl_array), | |
' Bwd Packet Length Std': np.std(bwd_pl_array), | |
'Flow Bytes/s': ((self.total_length_fwd_packets + self.total_length_bwd_packets) / flow_duration) * 1e6 if flow_duration > 0 else 0, | |
' Flow Packets/s': ((self.total_fwd_packets + self.total_bwd_packets) / flow_duration) * 1e6 if flow_duration > 0 else 0, | |
' Flow IAT Mean': np.mean(self.flow_iat), | |
' Flow IAT Std': np.std(self.flow_iat), | |
' Flow IAT Max': np.max(self.flow_iat), | |
' Flow IAT Min': np.min(self.flow_iat), | |
'Fwd IAT Total': sum(self.fwd_iat), | |
' Fwd IAT Mean': np.mean(self.fwd_iat), | |
' Fwd IAT Std': np.std(self.fwd_iat), | |
' Fwd IAT Max': np.max(self.fwd_iat), | |
' Fwd IAT Min': np.min(self.fwd_iat), | |
'Bwd IAT Total': sum(self.bwd_iat), | |
' Bwd IAT Mean': np.mean(self.bwd_iat), | |
' Bwd IAT Std': np.std(self.bwd_iat), | |
' Bwd IAT Max': np.max(self.bwd_iat), | |
' Bwd IAT Min': np.min(self.bwd_iat), | |
'Fwd PSH Flags': 0, | |
' Bwd PSH Flags': 0, | |
' Fwd URG Flags': 0, | |
' Bwd URG Flags': 0, | |
' Fwd Header Length': self.fwd_header_length, | |
' Bwd Header Length': self.bwd_header_length, | |
'Fwd Packets/s': (self.total_fwd_packets / flow_duration) * 1e6 if flow_duration > 0 else 0, | |
' Bwd Packets/s': (self.total_bwd_packets / flow_duration) * 1e6 if flow_duration > 0 else 0, | |
' Min Packet Length': np.min(all_pl_array), | |
' Max Packet Length': np.max(all_pl_array), | |
' Packet Length Mean': np.mean(all_pl_array), | |
' Packet Length Std': np.std(all_pl_array), | |
' Packet Length Variance': np.var(all_pl_array), | |
'FIN Flag Count': self.fin_flag_count, | |
' SYN Flag Count': self.syn_flag_count, | |
' RST Flag Count': self.rst_flag_count, | |
' PSH Flag Count': self.psh_flag_count, | |
' ACK Flag Count': self.ack_flag_count, | |
' URG Flag Count': self.urg_flag_count, | |
' CWE Flag Count': self.cwe_flag_count, | |
' ECE Flag Count': self.ece_flag_count, | |
' Down/Up Ratio': (self.total_fwd_packets / self.total_bwd_packets) if self.total_bwd_packets > 0 else 0, | |
' Average Packet Size': (np.mean(all_pl_array)) if len(all_pl_array) > 0 else 0, | |
' Avg Fwd Segment Size': (self.total_length_fwd_packets / self.total_fwd_packets) if self.total_fwd_packets > 0 else 0, | |
' Avg Bwd Segment Size': (self.total_length_bwd_packets / self.total_bwd_packets) if self.total_bwd_packets > 0 else 0, | |
' Fwd Header Length.1': self.fwd_header_length, | |
'Fwd Avg Bytes/Bulk': 0, | |
' Fwd Avg Packets/Bulk': 0, | |
' Fwd Avg Bulk Rate': 0, | |
' Bwd Avg Bytes/Bulk': 0, | |
' Bwd Avg Packets/Bulk': 0, | |
'Bwd Avg Bulk Rate': 0, | |
'Subflow Fwd Packets': self.total_fwd_packets, | |
' Subflow Fwd Bytes': self.total_length_fwd_packets, | |
' Subflow Bwd Packets': self.total_bwd_packets, | |
' Subflow Bwd Bytes': self.total_length_bwd_packets, | |
'Init_Win_bytes_forward': self.init_win_bytes_forward or 0, | |
' Init_Win_bytes_backward': self.init_win_bytes_backward or 0, | |
' act_data_pkt_fwd': self.act_data_pkt_fwd, | |
' min_seg_size_forward': self.min_seg_size_forward or 0, | |
'Active Mean': 0, | |
' Active Std': 0, | |
' Active Max': 0, | |
' Active Min': 0, | |
'Idle Mean': 0, | |
' Idle Std': 0, | |
' Idle Max': 0, | |
' Idle Min': 0, | |
} | |
for feature in FEATURE_NAMES: | |
if feature not in features: | |
features[feature] = 0 | |
return features | |
def get_all_interfaces(): | |
""" | |
Get all available network interfaces with their IP addresses. | |
""" | |
try: | |
interfaces = net.interfaces() | |
excluded_interfaces = ['lo', 'lo0', 'bridge', 'docker', 'vmnet'] | |
available_interfaces = [] | |
for iface in interfaces: | |
if any(excluded in iface for excluded in excluded_interfaces): | |
continue | |
try: | |
addrs = net.ifaddresses(iface) | |
ip_info = addrs.get(net.AF_INET) | |
if ip_info: | |
ip_addr = ip_info[0].get('addr', 'N/A') | |
available_interfaces.append((iface, ip_addr)) | |
else: | |
available_interfaces.append((iface, 'N/A')) | |
except ValueError: | |
continue | |
return available_interfaces | |
except Exception as e: | |
print(f"Error getting network interfaces: {e}") | |
return [] | |
def capture_packets(interface_name, packet_queue, stop_event): | |
try: | |
capture = pyshark.LiveCapture(interface=interface_name) | |
for packet in capture.sniff_continuously(): | |
if stop_event.is_set(): | |
break | |
packet_queue.put(packet) | |
except Exception as e: | |
print(f"Error capturing packets: {e}") | |
NUM_THREADS = 4 # Number of threads for packet processing | |
def start_processing_threads(packet_queue, flow_dict, pipeline, stats): | |
""" | |
Start multiple threads to process packets in parallel. | |
""" | |
for _ in range(NUM_THREADS): | |
thread = threading.Thread( | |
target=process_packets, | |
args=(packet_queue, flow_dict, pipeline, stats), | |
daemon=True | |
) | |
thread.start() | |
def process_packets(packet_queue, flow_dict, pipeline, stats): | |
while True: | |
try: | |
try: | |
packet = packet_queue.get(timeout=1) | |
except Empty: | |
continue | |
if not hasattr(packet, 'ip'): | |
if VERBOSE: | |
print(f"Skipping non-IP packet: {packet}") | |
continue | |
# Extract packet information outside the lock | |
try: | |
if not hasattr(packet.ip, 'src') or not hasattr(packet.ip, 'dst'): | |
if VERBOSE: | |
print(f"Packet missing IP addresses: {packet}") | |
continue | |
src_ip = packet.ip.src | |
dst_ip = packet.ip.dst | |
# Get port information | |
if hasattr(packet, 'tcp'): | |
src_port = int(packet.tcp.srcport) | |
dst_port = int(packet.tcp.dstport) | |
protocol = 'TCP' | |
elif hasattr(packet, 'udp'): | |
src_port = int(packet.udp.srcport) | |
dst_port = int(packet.udp.dstport) | |
protocol = 'UDP' | |
else: | |
continue | |
# Create flow keys | |
forward_key = (src_ip, src_port, dst_ip, dst_port, protocol) | |
backward_key = (dst_ip, dst_port, src_ip, src_port, protocol) | |
# Update stats first | |
stats.update_stats(False) | |
# Now use the lock when accessing flow_dict | |
with flow_dict_lock: | |
# Get or create flow | |
if forward_key in flow_dict: | |
flow = flow_dict[forward_key] | |
direction = 'forward' | |
elif backward_key in flow_dict: | |
flow = flow_dict[backward_key] | |
direction = 'backward' | |
else: | |
flow = Flow(src_ip, src_port, dst_ip, dst_port, protocol) | |
flow_dict[forward_key] = flow | |
direction = 'forward' | |
# Add packet to flow while still holding the lock | |
flow.add_packet(packet, direction) | |
# Check for expired flows while holding the lock | |
for flow_key, flow in list(flow_dict.items()): | |
if flow.is_expired(timeout=60): | |
try: | |
# Extract features and make prediction | |
features = flow.compute_features() | |
features_df = pd.DataFrame([features]) | |
features_df = features_df[pipeline['selected_features']] | |
X = features_df.copy() | |
X = pipeline['variance_selector'].transform(X) | |
X = pipeline['scaler'].transform(X) | |
X = pipeline['selector'].transform(X) | |
prediction = pipeline['model'].predict(X) | |
# Log prediction | |
src_ip, src_port, dst_ip, dst_port, proto = flow_key | |
status = 'DDoS' if prediction[0] == 1 else 'Normal' | |
packets = flow.total_fwd_packets + flow.total_bwd_packets | |
print(f"[{time.strftime('%H:%M:%S')}] {src_ip}:{src_port} β {dst_ip}:{dst_port} | {proto} | Packets: {packets} | Status: {status}") | |
except Exception as e: | |
print(f"Prediction error: {e}") | |
finally: | |
del flow_dict[flow_key] | |
except AttributeError as e: | |
if VERBOSE: | |
print(f"Packet parsing error: {e}") | |
continue | |
except Exception as e: | |
if VERBOSE: | |
print(f"Processing error: {e}") | |
continue | |
def process_packets(packet_queue, flow_dict, pipeline, stats): | |
while True: | |
try: | |
try: | |
packet = packet_queue.get(timeout=1) | |
except Empty: | |
continue | |
if not hasattr(packet, 'ip'): | |
if VERBOSE: | |
print(f"Skipping non-IP packet: {packet}") | |
continue | |
try: | |
# Extract flow information with error checking | |
if not hasattr(packet.ip, 'src') or not hasattr(packet.ip, 'dst'): | |
if VERBOSE: | |
print(f"Packet missing IP addresses: {packet}") | |
continue | |
src_ip = packet.ip.src | |
dst_ip = packet.ip.dst | |
# Get port information with better error handling | |
if hasattr(packet, 'tcp'): | |
try: | |
src_port = int(packet.tcp.srcport) | |
dst_port = int(packet.tcp.dstport) | |
protocol = 'TCP' | |
except (AttributeError, ValueError) as e: | |
if VERBOSE: | |
print(f"Error getting TCP ports: {e}") | |
continue | |
elif hasattr(packet, 'udp'): | |
try: | |
src_port = int(packet.udp.srcport) | |
dst_port = int(packet.udp.dstport) | |
protocol = 'UDP' | |
except (AttributeError, ValueError) as e: | |
if VERBOSE: | |
print(f"Error getting UDP ports: {e}") | |
continue | |
else: | |
if VERBOSE: | |
print(f"Packet is neither TCP nor UDP: {packet}") | |
continue | |
# Process flow and update statistics | |
forward_key = (src_ip, src_port, dst_ip, dst_port, protocol) | |
backward_key = (dst_ip, dst_port, src_ip, src_port, protocol) | |
# Update stats first | |
stats.update_stats(False) | |
# Get or create flow | |
if forward_key in flow_dict: | |
flow = flow_dict[forward_key] | |
direction = 'forward' | |
elif backward_key in flow_dict: | |
flow = flow_dict[backward_key] | |
direction = 'backward' | |
else: | |
flow = Flow(src_ip, src_port, dst_ip, dst_port, protocol) | |
flow_dict[forward_key] = flow | |
direction = 'forward' | |
flow.add_packet(packet, direction) | |
except AttributeError as e: | |
if VERBOSE: | |
print(f"Packet parsing error: {e}") | |
print(f"Packet details: {packet}") | |
continue | |
except Exception as e: | |
if VERBOSE: | |
print(f"Processing error: {e}") | |
import traceback | |
print(traceback.format_exc()) | |
continue | |
def select_interface(interfaces): | |
"""Select network interface for packet capture""" | |
if len(interfaces) == 1: | |
# If only one active interface, automatically select it | |
interface_name = interfaces[0][0] | |
print(f"Automatically selected interface: {interface_name} (IP: {interfaces[0][1]})") | |
return interface_name | |
# Display multiple active interfaces and let user select | |
print("\nAvailable Network Interfaces:") | |
for idx, (iface, ip_addr) in enumerate(interfaces): | |
print(f"{idx}: {iface} (IP: {ip_addr})") | |
while True: | |
try: | |
selected_idx = int(input("\nSelect interface index for capture: ")) | |
if 0 <= selected_idx < len(interfaces): | |
return interfaces[selected_idx][0] | |
print("Invalid selection. Try again.") | |
except ValueError: | |
print("Please enter a valid number.") | |
def predict_flow(flow, pipeline): | |
"""Make prediction for a single flow""" | |
features = flow.compute_features() | |
features_df = pd.DataFrame([features]) | |
features_df = features_df[pipeline['selected_features']] | |
X = features_df.copy() | |
X = pipeline['variance_selector'].transform(X) | |
X = pipeline['scaler'].transform(X) | |
X = pipeline['selector'].transform(X) | |
return pipeline['model'].predict(X)[0] | |
def start_capture_threads(interface_name, packet_queue, flow_dict, pipeline, stats, stop_event): | |
"""Start capture and processing threads""" | |
capture_thread = threading.Thread( | |
target=capture_packets, | |
args=(interface_name, packet_queue, stop_event), | |
daemon=True | |
) | |
processing_thread = threading.Thread( | |
target=process_packets, | |
args=(packet_queue, flow_dict, pipeline, stats), | |
daemon=True | |
) | |
capture_thread.start() | |
processing_thread.start() | |
return [capture_thread, processing_thread] | |
def cleanup(stop_event, threads, stats): | |
"""Clean up threads and display final statistics""" | |
stop_event.set() | |
for thread in threads: | |
thread.join(timeout=5) | |
stats.print_stats() | |
print("\nCapture stopped.") | |
def main(): | |
print("Network Traffic DDoS Monitor") | |
# Verify features first thing in main | |
verify_features() | |
# Initialize statistics | |
stats = PacketStats() | |
# Initialize queue and flow tracking | |
packet_queue = Queue(maxsize=QUEUE_SIZE) | |
flow_dict = {} | |
# Get interfaces and setup capture | |
interfaces = [(iface, ip) for iface, ip in get_all_interfaces() if ip != 'N/A'] | |
if not interfaces: | |
print("No active network interfaces found.") | |
return | |
interface_name = select_interface(interfaces) | |
print(f"\nStarting capture on: {interface_name}") | |
# Start capture | |
stop_event = threading.Event() | |
threads = start_capture_threads(interface_name, packet_queue, flow_dict, pipeline, stats, stop_event) | |
try: | |
while True: | |
time.sleep(10) | |
stats.print_stats() | |
except KeyboardInterrupt: | |
print("\nStopping capture...") | |
finally: | |
cleanup(stop_event, threads, stats) | |
# Feature extraction and prediction | |
print("\nProcessing captured network flows...") | |
features_list = [] | |
predictions = [] | |
for flow_key, flow in flow_dict.items(): | |
try: | |
# Get features | |
features = flow.compute_features() | |
features_list.append(features) | |
# Create DataFrame with only the required features | |
features_df = pd.DataFrame([features]) | |
feature_vector = pd.DataFrame(columns=pipeline['selected_features']) | |
for feature in pipeline['selected_features']: | |
feature_vector[feature] = features_df.get(feature, 0) | |
# Apply the pipeline transformations | |
X = pipeline['variance_selector'].transform(feature_vector) | |
X = pipeline['scaler'].transform(X) | |
X = pipeline['selector'].transform(X) | |
# Make prediction | |
prediction = pipeline['model'].predict(X) | |
predictions.append(prediction[0]) | |
# Print prediction | |
src_ip, src_port, dst_ip, dst_port, proto = flow_key | |
print(f"Flow: {src_ip}:{src_port} -> {dst_ip}:{dst_port} ({proto})") | |
print(f"Prediction: {'BENIGN' if prediction[0] == 0 else 'DDoS'}") | |
print(f"Total packets: Forward={flow.total_fwd_packets}, Backward={flow.total_bwd_packets}") | |
print("-" * 50) | |
except Exception as e: | |
print(f"Error processing flow: {str(e)}") | |
# Save results if we have any | |
if features_list: | |
df = pd.DataFrame(features_list) | |
df['Prediction'] = predictions | |
output_file = 'network_traffic_predictions.csv' | |
df.to_csv(output_file, index=False) | |
print(f"\nFeatures and predictions saved to {output_file}") | |
print(f"Total flows captured: {len(features_list)}") | |
else: | |
print("No network flows were captured.") | |
def streamlit_app(): | |
st.title("Real-Time Network Traffic DDoS Monitor") | |
st.markdown("Monitor your network traffic in real time and detect potential DDoS attacks.") | |
# Initialize session state | |
if 'is_scanning' not in st.session_state: | |
st.session_state.is_scanning = False | |
if 'packet_queue' not in st.session_state: | |
st.session_state.packet_queue = Queue(maxsize=QUEUE_SIZE) | |
if 'flow_dict' not in st.session_state: | |
st.session_state.flow_dict = {} | |
if 'stats' not in st.session_state: | |
st.session_state.stats = PacketStats() | |
if 'stop_event' not in st.session_state: | |
st.session_state.stop_event = threading.Event() | |
if 'selected_interface' not in st.session_state: | |
st.session_state.selected_interface = None | |
# Get interfaces and handle interface selection | |
interfaces = [(iface, ip) for iface, ip in get_all_interfaces() if ip != 'N/A'] | |
if not interfaces: | |
st.error("No active network interfaces found.") | |
return | |
# Automatic interface selection if only one available | |
if len(interfaces) == 1: | |
if not st.session_state.selected_interface: | |
st.session_state.selected_interface = interfaces[0][0] | |
st.info(f"Using network interface: {st.session_state.selected_interface} (IP: {interfaces[0][1]})") | |
else: | |
# Show selection box only if multiple interfaces available | |
st.session_state.selected_interface = st.selectbox( | |
"Select Network Interface", | |
[iface[0] for iface in interfaces], | |
key='interface_select' | |
) | |
# Control buttons in the same row | |
col1, col2 = st.columns(2) | |
with col1: | |
if st.button("Start Scanning", key='start_button'): | |
st.session_state.is_scanning = True | |
st.session_state.stop_event.clear() | |
threads = start_capture_threads( | |
st.session_state.selected_interface, | |
st.session_state.packet_queue, | |
st.session_state.flow_dict, | |
pipeline, | |
st.session_state.stats, | |
st.session_state.stop_event | |
) | |
st.session_state.threads = threads | |
with col2: | |
if st.button("Stop Scanning", key='stop_button'): | |
st.session_state.is_scanning = False | |
if hasattr(st.session_state, 'stop_event'): | |
st.session_state.stop_event.set() | |
if hasattr(st.session_state, 'threads'): | |
for thread in st.session_state.threads: | |
thread.join(timeout=5) | |
# Display statistics | |
if st.session_state.is_scanning: | |
stats_container = st.container() | |
with stats_container: | |
st.markdown("### Statistics:") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric("Total Packets", st.session_state.stats.total_packets) | |
with col2: | |
st.metric("DDoS Flows", st.session_state.stats.ddos_flows) | |
with col3: | |
st.metric("Benign Flows", st.session_state.stats.benign_flows) | |
# Display active flows | |
flow_data = [] | |
for flow_key, flow in st.session_state.flow_dict.items(): | |
src_ip, src_port, dst_ip, dst_port, protocol = flow_key | |
packets = flow.total_fwd_packets + flow.total_bwd_packets | |
flow_data.append([src_ip, src_port, dst_ip, dst_port, protocol, packets]) | |
if flow_data: | |
st.markdown("### Active Flows") | |
df = pd.DataFrame(flow_data, | |
columns=["Src IP", "Src Port", "Dst IP", "Dst Port", "Protocol", "Packets"]) | |
st.dataframe(df, use_container_width=True) | |
if st.session_state.is_scanning: | |
time.sleep(0.01) | |
st.rerun() | |
if __name__ == "__main__": | |
st.set_page_config( | |
page_title="DDoS Monitor", | |
page_icon="π", | |
layout="wide", | |
initial_sidebar_state="collapsed" | |
) | |
streamlit_app() |